Sunday, August 11, 2024

A two times two week

There were no contests that I wanted to mention last week, but in the week before that I forgot to mention the 3rd Universal Cup Stage 5: Moscow (problems, results, top 5 on the left). Team HoMaMaOvO was the only one to wrap things up before the last hour, but they still lost out to USA1 because they were a bit slower on the easier problems: this was one of the relatively rare cases where the ICPC and the AtCoder penalty time formulas place different teams in the first place. Congratulations to both teams, to team Polish Mafia who were as fast but had much more incorrect attempts, at to team MIT Isn't Training who were the only remaining team to AK!

What I did not forget to mention were two nice problems. The first one came from the EGOI: There is a sequence of n bits (each 0 or 1) ai that is unknown to you. In addition, there is a permutation pj of size n that is known to you. Your goal is to apply this permutation to this sequence, in other words to construct the new sequence bi=api. Your solution will be invoked as follows. On the first run, it will read the run number (0) and the permutation pj, print an integer w, and exit. It will then be executed w more times. On the first of those runs, it will read the run number (1) and the permutation pj again, and then it will read the sequence ai from left to right, and after reading the i-th bit, it has to write the new value for the i-th bit before reading the next one. After processing the n bits, your program must exit. On the second of those runs, it will read the run number (2) and pj again, and then read ai (potentially modified during the first run) from right to left, and it has to write the new value for the i-th bit before reading the previous one. On the third of those runs, it goes left to right again, and so on. After the w-th run is over, we must have bi=api, where bi is the final state of the sequence, and ai is the original state of the sequence.

First, consider the case n=2. If the permutation is an identity permutation, we can just print w=0, and we're done. The only other case is when the permutation is a swap. Even in that case, there is actually not that many options for what we can do on the first pass. First, notice that we must not lose information after the first pass: all 2n possible sets of values of ai before and after the first pass must be in a one to one bijective relationship, as otherwise we would have two initial states map to the same state after the first pass, and since our program is then restarted and we have no other memory except the current state of ai, we would not be able to distinguish those two initial states, but the end results for them must be different.

Therefore after reading a0 we have only two options: we either do not change it, or we change it to the opposite value a0+1 (here and below we use addition modulo 2). But note that adding 1 on the first pass makes no difference if we have a second pass, since we could just add 1 on reading in the second pass instead. Therefore, without loss of generality we do not change a0 on the first pass. For a1 we have a few more options since we can also take a0 into account. More specifically, in each of the two cases a0=0 and a0=1 we either keep a1 unchanged or add 1 to it. If we take the same decision in both of those cases, then the whole first pass has been useless, as we can make the same adjustment on reading in the second pass. Therefore we must take different decisions. Which one we take in which case is also irrelevant, since they differ by adding a constant, which can be done in the second pass instead. Therefore once again we can essentially only do one thing: write a1+a0 as the new value for a1.

By the same argument, on the second pass where we go from right to left we keep a1 unchanged and write a1+a0 as the new value for a0, and so on. After three passes, we actually obtain the well-known algorithm to swap two values without using additional memory (note that + and - are the same thing modulo 2):

  • a1=a1+a0
  • a0=a1-a0
  • a1=a1-a0

This means that we have applied the swap as needed. We could only do one thing for n=2, but this thing turned out to be exactly what we needed when w=3!

Now consider the case of higher n, but when the given permutation is the reverse permutation. The reverse permutation consists of independent swaps: we need to swap the 0th and the (n-1)-th bits, the 1st and the (n-2)-th, and so on. Therefore we can simply apply the above trick for swapping a pair for all of those swaps, and do it in parallel during the same 3 passes, therefore solving this case with w=3 as well.

We can do the same for any permutation that consists of independent swaps — in other words for any permutation of order 2. But how to handle the general case? It turns out that any permutation can be represented as a product of two permutations of order 2! To see how to do it, we can first notice that it suffices to represent a cycle of arbitrary length as such a product, as several cycles can be handled independently. We can then consider what happens when we multiply the following two permutations: the first permutation swaps the 0th and 1st elements, then the 2nd and 3rd elements, and so on; the second permutation keeps the 0th element in place, swaps the 1st and 2nd elements, the 3rd and 4th elements, and so on. It is not hard to see that every swap of the second permutation will swap two elements of different cycles, which means that those cycles will merge, which in turn means that in the end we will have one big cycle. The nodes along this cycle will not come in the order 0, 1, 2, ..., but it does not matter since we can renumber them arbitrarily to obtain a solution for any cycle.

This fact can be used to solve the general case with w=6 by applying each of the two permutations of order 2 using 3 passes. However, we can do one better and achieve w=5 if we notice that the last pass of applying the first permutation and the first pass of applying the second permutation can be merged into one. In order to see this, notice that we can make both of those passes go from left to right, and then each of them would find a new value for a certain bit using the old values of this bit and all previous bits; since our memory is not erased within one pass, we might as well compute the new value for that bit for the first pass of the second permutation using the values that would have been computed by the last pass of the first permutation instead, and write that one directly.

Going from w=5 to the optimal w=3 is a bit more technical, so I will not describe it in detail. To come up with my own solution for w=3 I relied on a more sophisticated version of the argument from the solution for n=2. After the second pass, we must leave the bits in such a state that the final state of each bit depends only on the state of this bit and the previous bits (since the third pass goes from left to right). And there are actually not too many different things that we can do during the first pass if we need to be able to leave the bits in such a state after the second pass. You can also check out alernative approaches in this Codeforces comment, or in the editorial

The second problem came from Codeforces: you are given an empty n times m grid (4 <= n, m <= 10). You need to write all integers from 1 to nm exactly once in the grid. You will then play the following game on this grid as the second player and need to always win. The first player takes any cell of the grid for themselves. Then the second player takes any cell that is not taken yet, but is adjacent (shares a side) to a taken cell, for themselves. Then the first player takes any cell that is not taken yet, but is adjacent (shares a side) to a taken cell, for themselves, and so on until all cells are taken. The second player wins if the sum of the numbers of the cells they have in the end is smaller than the sum of the numbers of the cells of the first player.

I did not solve this problem, but it turned out that I was on the right track :) On the left you can see the picture I had in my notes during the contest, where I placed 1, 2 and 3, and was trying to prove that if the starting player takes one of them, I can always take the remaining two for myself. On the right you can see the picture from the excellent editorial, to which I refer you for the actual solution, as I do not have much to add :)

Thanks for reading, and check back for this week's summary!

No comments:

Post a Comment