We will discuss the solution to Robot Writing from AIO 2025. This is a very difficult problem designed to challenge the very top students in Australia, so do not worry if you are struggling to understand the solution!
Trying examples by hand is a very useful tool when problem solving. For simplicity, our examples don't include the final movement (the last steps 2 and 3) made by the robot because these do not impact the output of the robot.
Let's start by watching the example below:
One thing that I notice from this example is that the output is weakly increasing, meaning that each new output is greater than or equal to the previous output.
This is not a coincidence: the robot swaps tiles if the new tile has a smaller value, meaning that bigger values follow
the robot.
Specifically, at each step the robot prints the largest value it has seen so far.
Therefore the answer is NO if the target sequence S ever decreases.
A second and simpler observation is that the output will only ever contain numbers found on the tiles. If the target sequence S ever has a value which isn't on any tile, we know that the answer is NO.
In every subtask we begin by checking the above two conditions before moving onto the solution for that subtask.
In this subtask we know that T = [1, 2, 3, 4, ...]. Let's look at another example:
Let's investigate some features of this example:
skipany values. This is because the robot always moves to consecutive tiles and in this subtask consecutive tiles have consecutive values. The answer will always be NO if the output sequence
skipsa value.
Putting all of this together, our solution to subtask 1 is:
skipsany values the answer is NO.
Below is my code in Python and C++ for this subtask. I suggest trying to code the solution yourself and only look at our code after you finish or if you get stuck. Remember to check every NO case!
import sys
# N is the number of tiles.
N = 0
# T contains the values on the tiles. Note that the list starts from 0, and so
# the values are T[0] to T[N-1].
T = []
# M is the length of target sequence.
M = 0
# S contains the target sequence. Note that the list starts from 0, and so the
# values are S[0] to S[M-1].
S = []
# Read the values of N, S, M, and T.
N = int(input().strip())
T = list(map(int, input().strip().split()))
M = int(input().strip())
S = list(map(int, input().strip().split()))
# This function prints NO and then ends the program
def NO():
print("NO")
sys.exit(0)
# Check the first two cases
for i in range(M-1):
if S[i] > S[i+1]:
# The target sequence is not non-decreasing
NO()
for s in S:
if s not in T:
# There is a value in the target sequence which does not appear on any tile
NO()
# Check the three cases from our subtask 1 solution
for i in range(M-1):
if S[i+1] > S[i]+1:
# The target sequence 'skips' a value
NO()
# For the second and third cases we must count how many times each value occurs
num_occurrences = [0 for i in range(N+1)] # num_occurrences[i] will store the number of i's in the target sequence
for s in S:
num_occurrences[s] += 1
for s in S:
# Except the largest value, everything must occur an odd number of times
if s != S[-1] and num_occurrences[s]%2 == 0:
NO()
if num_occurrences[1] > 1:
NO() # 1 cannot occur more than once
print("YES")
#include <cstdio>
#include <cstdlib> // for exit()
#include <algorithm> // for std::find
/* N is the number of tiles. */
int N;
/*
* T contains the values on the tiles. Note that the array starts from 0, and
* so the values are T[0] to T[N-1].
*/
int T[200005];
/* M is the length of target sequence. */
int M;
/*
* S contains the target sequence. Note that the array starts from 0, and so
* the values are S[0] to S[M-1].
*/
int S[200005];
/* num_occurrences[i] will store the number of i's in the target sequence */
int num_occurrences[200005];
/* This function prints NO and then ends the program */
int NO() {
printf("NO\n");
exit(0);
}
int main() {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%d", &T[i]);
}
scanf("%d", &M);
for (int i = 0; i < M; i++) {
scanf("%d", &S[i]);
}
/* Check the first two cases */
for (int i = 0; i < M-1; i++) {
if (S[i] > S[i+1]) {
// The target sequence is not non-decreasing
NO();
}
}
for (int i = 0; i < M; i++) {
int s = S[i];
if (std::find(T, T+N, s) == T+N) {
// There is a value in the target sequence which does not appear on any tile
NO();
}
}
/* Check the three cases from our subtask 1 solution */
for (int i = 0; i < M-1; i++) {
if (S[i+1] > S[i]+1) {
// The target sequence 'skips' a value
NO();
}
}
// For the second and third cases we must count how many times each value occurs
// num_occurrences[i] will store the number of i's in the target sequence
for (int i = 0; i < M; i++) {
int s = S[i];
num_occurrences[s] += 1;
}
for (int i = 0; i < M; i++) {
int s = S[i];
// Except the largest value, everything must occur an odd number of times
if (s != S[M-1] && num_occurrences[s]%2 == 0) {
NO();
}
}
if (num_occurrences[1] > 1) {
NO();
}
printf("YES\n");
}
Let's try some examples using the above tiles.
In the example, we now know that:
One last example for this subtask (I promise!): S = [2, 2, 2, 5]. This does not break any rules so far: x ≥ d, the parities are the same, and there are no larger tiles between 1 and 4. But the answer is still NO! The reason to this is similar to the final case in subtask 1 where we discovered that 1 could not appear more than once in the target sequence. In our example, both tiles on either side of 2 have larger values and so it is impossible to output 2 more than once. (Similarly, if a tile is on the end, then you should check if the single adjacent tile has a larger value.)
Putting this all together, we must check:
Below is my code in C++ and Python. As a challenge, prove that my code has a fast enough time complexity to pass subtask 3 (this may surprise you because of the nested for loops!)
import sys
N = int(input().strip())
T = list(map(int, input().strip().split()))
M = int(input().strip())
S = list(map(int, input().strip().split()))
# This function prints NO and then ends the program
def NO():
print("NO")
sys.exit(0)
# Check the first two cases
for i in range(M-1):
if S[i] > S[i+1]:
# The target sequence is not non-decreasing
NO()
# To check the second case we create a helper list called in_T
in_T = [-1 for i in range(1000001)] # in_T[i] will be the tile number of the tile with value i, or -1 if there is no such tile
for i in range(N):
in_T[T[i]] = i
for s in S:
if in_T[s] == -1:
NO()
# Check the cases from our subtask 2 solution
# To help with this, we must count how many times each value occurs
num_occurrences = [0 for i in range(1000001)] # num_occurrences[i] will store the number of i's in the target sequence
for s in S:
num_occurrences[s] += 1
for i in range(M-1):
x = num_occurrences[S[i]]
tile1 = in_T[S[i]]
tile2 = in_T[S[i+1]]
if S[i] != S[i+1]:
d = abs(in_T[S[i]] - in_T[S[i+1]])
if x < d or x%2 != d%2:
NO()
for tile in range(min(tile1, tile2)+1, max(tile1, tile2)):
if T[tile] > S[i]:
# there is a larger tile between the two tiles!
NO()
# Check if S[i] has larger tiles on both sides
if tile1 == 0 or T[tile1-1] > T[tile1]:
# larger on the left
if tile1 == N-1 or T[tile1+1] > T[tile1]:
# larger on the right
if x != 1:
NO()
print("YES")
#include <cstdio>
#include <cstdlib> // for exit()
#include <cmath> // for abs
#include <algorithm> // for std::min, std::max, and std::fill
int N;
int T[200005];
int M;
int S[200005];
/* num_occurrences[i] will store the number of i's in the target sequence */
const int MAX_VALUE = 1000001;
int num_occurrences[MAX_VALUE];
/* in_T[i] will be the tile number of the tile with value i, or -1 if there is no such tile */
int in_T[MAX_VALUE];
/* This function prints NO and then ends the program */
int NO() {
printf("NO\n");
exit(0);
}
int main(void) {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%d", &T[i]);
}
scanf("%d", &M);
for (int i = 0; i < M; i++) {
scanf("%d", &S[i]);
}
/* Check the first two cases */
for (int i = 0; i < M-1; i++) {
if (S[i] > S[i+1]) {
// The target sequence is not non-decreasing
NO();
}
}
// To check the second case we create a helper list called in_T
// in_T[i] will be the tile number of the tile with value i, or -1 if there is no such tile
std::fill(in_T, in_T+MAX_VALUE, -1);
for (int i = 0; i < N; i++) {
in_T[T[i]] = i;
}
for (int i = 0; i < M; i++) {
int s = S[i];
if (in_T[s] == -1) {
NO();
}
}
/* Check the cases from our subtask 2 solution */
// To help with this, we must count how many times each value occurs
for (int i = 0; i < M; i++) {
int s = S[i];
num_occurrences[s]++;
}
for (int i = 0; i < M-1; i++) {
int x = num_occurrences[S[i]];
int tile1 = in_T[S[i]];
int tile2 = in_T[S[i+1]];
if (S[i] != S[i+1]) {
int d = abs(in_T[S[i]] - in_T[S[i+1]]);
if (x < d || x%2 != d%2) {
NO();
}
for (int tile = std::min(tile1, tile2+1); tile < std::max(tile1, tile2); tile++) {
if (T[tile] > S[i]) {
// there is a larger tile between the two tiles!
NO();
}
}
}
// Check if S[i] has larger tiles on both sides
if (tile1 == 0 || T[tile1-1] > T[tile1]) {
// larger on the left
if (tile1 == N-1 || T[tile1+1] > T[tile1]) {
// larger on the right
if (x != 1) {
NO();
}
}
}
}
printf("YES\n");
}
The first three subtasks of this problem were very challenging, but the final two subtasks are even harder: do not worry if you struggle to understand the solution!
In subtasks 1-3 the tiles all had distinct values which meant that we didn't have many choices for where the robot could go. In the final 2 subtasks there can be multiple tiles with the same value, which means there can be multiple possible starting tiles and multiple options for where to go from there. Our solution needs to efficiently check all of these.
Let's look at another example:
Our target sequence is S = [2, 2, 2, 2, 2, 3, 3, 3, 3, 4].
We will determine what the robot can output bit by bit. Specifically, we say that some tile i is possible if it is possible for the robot to reach tile i for the first time while outputting a prefix of S. Additionally, the value on tile i should be outputted for the first time when this tile is reached. For example:
To solve subtasks 4 and 5 you can simulate this process in code. We begin by sorting the tiles by value (similarly to what I did in the example above). We then go through the tiles one at a time and check whether they are possible. To check whether a tile i is possible, look at the target sequence S and check whether the value on the tile appears in the sequence. If it doesn't, then the tile is not possible. If it does, look for the previous value in the sequence. For tile i to be possible, there must be a possible tile containing the previous value, and the path between the two tiles must satisfy all the conditions from the subtask 2/3 solution.
Depending on your time complexity, your solution will either pass subtask 4 or the full problem. We leave the specific details of these solutions as a challenge for the reader.
Below is my subtask 4 implementation (the worst case time complexity is O(NM) and so it will not pass subtask 3 or 5).import sys
N = int(input().strip())
T = list(map(int, input().strip().split()))
M = int(input().strip())
S = list(map(int, input().strip().split()))
MAX_VAL = 1000001
# This function prints NO and then ends the program
def NO():
print("NO")
sys.exit(0)
# Check the first two cases
for i in range(M-1):
if S[i] > S[i+1]:
# The target sequence is not non-decreasing
NO()
# To make certain parts of our implementation nicer we add pretend tiles to the start and end with a very large value
T = [MAX_VAL] + T + [MAX_VAL]
num_occurrences = [0 for i in range(MAX_VAL+1)] # num_occurrences[i] will store the number of i's in the target sequence
for s in S:
num_occurrences[s] += 1
prior_target_value = [0 for i in range(MAX_VAL)]
for i in range(1, len(S)):
if S[i-1] != S[i]:
prior_target_value[S[i]] = S[i-1]
is_possible = [False for i in range(len(T))]
# first check whether tiles containing S[0] are possible
for i, value in enumerate(T):
if value == S[0]:
if M > 1 and S[1] == S[0] and T[i-1] > T[i] and T[i+1] > T[i]:
# special case: not possible
pass
else:
is_possible[i] = True
# this for loop iterates over pairs of (index, value) from T in ascending order
for i, value in sorted(enumerate(T), key=lambda i: i[1]):
if num_occurrences[value] == 0:
continue
for end, diff in [(-1, -1), (len(T), 1)]:
for j in range(i+diff, end, diff):
d = abs(i-j)
x = num_occurrences[prior_target_value[value]]
if is_possible[j] and T[j] == prior_target_value[value] and d <= x and d%2 == x%2:
is_possible[i] = True
if T[j] > prior_target_value[value]:
break
for i, value in enumerate(T):
if is_possible[i] and value == S[-1]:
print("YES")
sys.exit(0)
print("NO")
#include <cstdio>
#include <algorithm> // for std::sort
#include <vector> // for std::vector
#include <utility> // for std::pair
using namespace std;
const int MAX_VAL = 1000001;
int N, M;
/* This function prints NO and then ends the program */
int NO() {
printf("NO\n");
exit(0);
}
int main() {
scanf("%d", &N);
std::vector<int> T(N);
for (int i = 0; i < N; i++) {
scanf("%d", &T[i]);
}
scanf("%d", &M);
std::vector<int> S(M);
for (int i = 0; i < M; i++) {
scanf("%d", &S[i]);
}
/* Check the first two cases */
for (int i = 0; i < M-1; i++) {
if (S[i] > S[i+1]) {
// The target sequence is not non-decreasing
NO();
}
}
// To make certain parts of our implementation nicer we add pretend tiles to the start and end with a very large value
T.insert(T.begin(), MAX_VAL);
T.push_back(MAX_VAL);
std::vector<int> num_occurrences(MAX_VAL+1, 0); // num_occurrences[i] will store the number of i's in the target sequence
for (int s : S) {
num_occurrences[s]++;
}
std::vector<int> prior_target_value(MAX_VAL, 0);
for (int i = 1; i < (int)S.size(); i++) {
if (S[i-1] != S[i]) {
prior_target_value[S[i]] = S[i-1];
}
}
std::vector<bool> is_possible(T.size(), false);
// first check whether tiles containing S[0] are possible
for (int i = 0; i < (int)T.size(); i++) {
int value = T[i];
if (value == S[0]) {
if (M > 1 && S[1] == S[0] && T[i-1] > T[i] && T[i+1] > T[i]) {
// special case: not possible
} else {
is_possible[i] = true;
}
}
}
std::vector<std::pair<int,int>> order;
for (int i = 0; i < (int)T.size(); i++) {
order.push_back(make_pair(T[i], i));
}
std::sort(order.begin(), order.end());
// this for loop iterates over pairs of (index, value) from T in ascending order
for (int k = 0; k < order.size(); k++) {
int i = order[k].second;
int value = order[k].first;
if (num_occurrences[value] == 0) continue;
for (int dir = 0; dir < 2; dir++) {
int end = (dir == 0 ? -1 : (int)T.size());
int diff = (dir == 0 ? -1 : 1);
for (int j = i + diff; j != end; j += diff) {
int d = abs(i - j);
int x = num_occurrences[prior_target_value[value]];
if (is_possible[j] && T[j] == prior_target_value[value] && d <= x && d % 2 == x % 2) {
is_possible[i] = true;
}
if (T[j] > prior_target_value[value]) break;
}
}
}
for (int i = 0; i < (int)T.size(); i++) {
int value = T[i];
if (is_possible[i] && value == S.back()) {
printf("YES\n");
return 0;
}
}
printf("NO\n");
}