Editorial for DMOPC '21 Contest 2 P6 - Strange Function
Submitting an official solution before solving the problem yourself is a bannable offence.
Author:
Throughout this editorial, we refer to as just
. We also use 1-indexing for all indices.
Subtask 1
For subtask 1, it suffices to simulate the function explicitly for each type
1
query and handle type 2
and 3
queries naively. However, the implementation of given in the problem statement is
, which is not fast enough. There are many ways to optimize
to
, one of which is to observe that all the indices
involved in a recursive call to
are indices where the suffix minimum changes and find these with e.g. a stack.
Time complexity:
Subtask 2
For subtask 2, observe that after one call to
, and similarly
for all
after
calls to
. Hence
converges to the identity permutation after only
calls to
, so we don't need to continue updating
after this point. To handle type
3
queries in constant time, we also maintain the inverse of and update this after each of the first
type
1
queries.
Time complexity:
Subtask 3
For subtask 3, we essentially need to efficiently simulate calls to
in a row.
Consider where the maximum element of ends up after
calls. Unless it gets "pushed" all the way to the right end of the permutation, it will never be selected as an index
in a call to
and thus it will move one index to the right each time. Hence if
initially, then
after
calls. As for the other elements, the maximum element does not interfere with their relative orders during a call to
. Consider deleting this maximum element, finding the final permutation if this maximum element didn't exist, and inserting it back into the final permutation at the correct position. Repeating this reasoning with the second maximum element, third maximum element, and so on, we can efficiently simulate these operations with a data structure such as an order statistic tree (policy based data structures in C++).
Time complexity:
Subtask 4
For subtask 4, we need to handle general type 2
queries.
Some observations/ideas that motivate the solution:
It might help to think of the queries as "given and
, what is
after
calls to
?" instead of trying to maintain
after each individual call to
. This suggests handling all the queries offline.
It also might help to find a simple way of thinking about what does. Two interpretations include:
- Consider drawing a histogram with
positions in a row and
square blocks at position
. Then
is equivalent to letting all the blocks get pushed horizontally by one unit, with a "wall" after the rightmost position preventing blocks from getting pushed after this point.
is also equivalent to one iteration of a bubble-sort like loop:
void f(int l,int r) { int i; for (i = r; i > l; i--) { if (p[i-1] > p[i]) swap(p[i-1],p[i]); } }
Now for the solution:
For a number , consider writing a
at index
if
and a
if
. Focusing on what
does to this binary "slice" of
, we can find that
calls to
is equivalent to moving all the
's by
to the right, then "pushing" any
's that overflowed the right end back into the array, forming a suffix of entirely
's. If we have the set of all
's in the original
stored in a data structure like a binary indexed tree, then we can check whether index
(
) is a
after
calls with two cases:
is not in the suffix. We can check if index
exists and is a
.
is in the suffix. This occurs if there are at least
's between
and
.
Using this, we can perform a simultaneous binary search on all the queries.
Remark: The second way of thinking about means that
is a "layer" of a bubble sorting network. A standard idea regarding sorting networks is the 0-1 principle. This might help motivate the simultaneous binary search approach above.
Time complexity:
Subtask 5
For subtask 5, we need to handle general type 3
queries.
We can process the queries in decreasing order of while maintaining a binary indexed tree with a
at index
if
and
otherwise, similarly to subtask 4. For each query, do a binary search on this binary indexed tree to find the length of the suffix that is entirely
's after
calls. All
's before this point get shifted by exactly
to the right. Using this and another binary indexed tree for sums of indices, we can find the sum of all indices
where
after
calls. By creating two events for each query, one at
and one at
, we can subtract these two sums to answer each query.
Time complexity:
Subtask 6
For subtask 6, we can combine the solutions for type 2
and 3
queries described in subtasks 4 and 5.
Time complexity:
Even Faster Solutions
It is actually possible to solve both type 2
and 3
queries in if we analyze what the above algorithms are really calculating.
During the simultaneous binary search for type 2
, if then the answer is simply
as it is in the converged area. Otherwise, case 1 is equivalent to checking if
and case 2 is equivalent to checking if the
th smallest
with
is at least
. It follows that the answer for a type
2
query is equal to
if
and
if
. These
th smallest queries can be answered by sweeping through
in decreasing order and maintaining an order statistic tree.
For type 3
, the binary search to find the length of the suffix is equivalent to finding the smallest index such that there are exactly
's between
and
. We can do this in
with order statistics as well.
There exist some simpler solutions for type
3
as well, based on observing that each number will always move right by until it reaches a suffix of numbers greater than itself. After that, it will move left every time a larger number before it "pushes" it to the left. Finding the number of times the number gets "pushed" to the left turns out to be equivalent to some order statistic queries too.
Time complexity:
Further Thoughts
There is a very unexpected connection between this problem and th smallest queries, for both type
2
and type 3
queries. It is not obvious that the type 2
formula above is indeed still a permutation of !
The solution presented above for type 3
queries can just as easily answer queries of the form 3 l r
: Output the sum of the indices with
. It is possible to generalize type
2
queries as well (2 l r
: Output the sum of all with
) with a solution based on splitting the sum into two parts: the numbers in the suffix and the numbers that are not, using some more data structures to handle these.
Comments