Imagine you start with some sequence of numbers, each between and . At any point, you can take an index , look at the item at that position, and insert the number immediately after it. That grows the sequence by one. You can do this as many times as you want, in any order, and then stop.
In this problem you are given the ending sequence: an integer (up to ) and the final sequence .
In the easy version (A1), your task is to find the shortest original sequence that could have generated the final sequence, and print its length.
In the hard version (A2), let be the A1 answer for a sequence . You have to print the sum of over all subsegments .
- Generate.
- Ad hoc problem.
- Staircase?
- Runs; pseudo-runs.
- Reverse; recursion.
- Plus zero or plus one.
- Greedy.
- [Examine Examples] (with small variations); [Work Backward]; [Write It Out].
- .
- Rightmost; sweep left-to-right or right-to-left; "exposed".
IdeaWorking backward: who generated whom?AC
Start with the sample test cases. The first one is : clearly it could have started as just — the generates a , the generates a , and so on. The answer is . The second one is : the items are separated by two, and we only ever insert by adding one, so no item could have generated any other. The answer is .
So it seems like we're interested in the number of "runs". By a run I mean a staircase-like stretch where each item either repeats the previous number or goes up by exactly one, like (the plus-zero-or-plus-one shape from my observations).
A first conjecture, then: maybe the answer is just the number of runs. Let's test it on the last sample, , which is a strange and complicated case. The is a run by itself. Then is a run. Then is a run (a is allowed to generate two different 's). Then the last is a run. So the answer should be .
But the expected output says . The conjecture is wrong, and something surprising is going on: somehow that final doesn't need to be in the original sequence.
Given the ending sequence, what we really want is to reverse the process. If a number is followed by , then that pair is possible to undo. But it quickly gets complicated on other examples (I also played with cases such as ). One thing to realize is that after an item, you can generate any number of copies of the item plus one: a can be followed by several 's, and then in between, those 's can generate 's. So you can get sequences like entirely out of a single . In fact , the tail of the strange sample above, can also come entirely out of a single . The structure is something like a series of stacks, or maybe even a tree: which item generated which?
For a given item , which item to its left could have generated it? If some solution has any element generating , you can swap that choice for the closest element to its left that equals , and the solution still works. So greedy is safe: if attaches at all, attach it to the closest possible parent.
Under what conditions, exactly, can one item have generated another?
Let's work through the last sample. The first stands alone. Then comes the — and once you put the down, the before it cannot be used anymore: it can't have spawned the , and nothing later can attach to that with the sitting in between. So the dies. The next attaches to the . Then the arrives, and it kills both the and the the same way. And at the very end, the attaches to the far to its left — everything in between () is bigger than or equal to it, and all of that could have been generated after the was. That's why the answer is and not .
Sweep from left to right, and maintain a set of items that are still "alive", meaning still allowed to be the parent of a later item. To add : first delete everything in the set that is (those can no longer be used, with sitting after them). Then, if the largest item left in the set equals , attach to it, at no cost. Otherwise is a "root" — it had to be in the original sequence — so add one to the answer, and empty the whole set, because nothing before a root can be the parent of anything after it. Either way, insert into the set. The answer is the number of roots.
You kind of have to write this out to see what's going on. The whole sweep on :
item deleted (>= item) set left attaches to set after roots
9 - {} no: root {9} 1
8 9 {} no: root {8} 2
9 - {8} 8 {8,9} 2
2 9, 8 {} no: root {2} 3
3 - {2} 2 {2,3} 3
4 - {2,3} 3 {2,3,4} 3
4 4 {2,3} 3 {2,3,4} 3
5 - {2,3,4} 4 {2,3,4,5} 3
3 5, 4, 3 {2} 2 {2,3} 3Three roots: the first , the , and the . Everything else attaches to one of them.
S = empty set; answer = 0
for i = 1..n:
delete everything >= a[i] off the back of S
if (a[i] - 1) is not in S:
answer += 1 # a[i] is a root: it was in the original
S = empty set # nothing before a root can parent anything after
insert a[i] into S
output answerThis runs in , amortized: every item is inserted once and deleted at most once, so even the repeated deleting is fine. I got a wrong answer here at some point (my WA) — I honestly don't remember now what it was — but the version above is correct (my accepted A1).
IdeaA2: summing over all subsegmentsAC
To restate what A2 is asking: let be the A1 answer for a sequence , meaning the length of the shortest original sequence that could have generated . We now need the sum of over every subsegment . That is noticeably more complicated.
A few thoughts to start:
- Keep the same sweep as A1.
- The roots probably still matter.
- What happens when you add one new element in?
That last one feels like the question to chase:
What happens to all these subsegment answers when a single new element is added on the right?
Anchor on the right endpoint. Every subsegment ends somewhere, so as we sweep, we can consider just the windows that end exactly at the current position : handle those when we arrive at , then move the anchor to . Sweeping this way naturally partitions all the subsegments, with nothing missed and nothing double-counted.
And how does a window's answer change when arrives at its right end? The sweep from Idea 1 already tells us: either attaches to a parent (the closest to its left that is still alive), or it's a root. So run the exact same sweep, with the exact same set, but at each step record who the parent is. Call its position , with if is a root.
Now compare each window ending at against the matching window ending at :
- If is a root, it could not have been generated in any window: for every left endpoint .
- If has a parent at position : for windows with , the parent is inside the window, and the answer does not change. For windows with , the parent is cut off, so is a root of that window, and the answer goes up by one. There are such windows.
So the total over windows ending at depends only on the total over windows ending at , which means we can write it as a DP:
Let be the sum of over all windows ending at :
Then
where is the position of the parent that the Idea 1 sweep attaches to, or if is a root. The final answer is .
The example I was walking through and playing with was :
a = 1 2 2 3 2 3 4 1
P = 0 1 1 3 1 5 6 0
i - P = 1 1 2 1 4 1 1 8
dp = 1 2 4 5 9 10 11 19 answer = sum of dp = 61You can spot-check a column: the windows ending at position are , , , , , with answers .
S = empty set of (value, position)
for i = 1..n:
delete everything with value >= a[i] off the back of S
if S is empty or its largest value != a[i] - 1:
P[i] = 0 # root
S = empty set
else:
P[i] = position of that largest value
insert (a[i], i) into S
dp[0] = 0
for i = 1..n: dp[i] = dp[i-1] + (i - P[i])
answer = dp[1] + dp[2] + ... + dp[n] # use 64-bit integersThis is the same sweep as A1, with the same set, still . You have to use long longs so the sum doesn't overflow. Accepted (my A2 submission).
I actually first tried a more complicated way of computing , using mins and maxes over the items sitting between and its candidate parent. It got unwieldy. The A1 sweep was already computing exactly this parent the whole time — I should have reused what I'd already built instead of deriving it again from scratch.
Review
Call an item of the sequence a root if it could not have been generated by any item to its left. Every root must have been in the original sequence, and everything else can be generated starting from the roots. So for A1, the length of the shortest original sequence is the number of roots. The path:
- Play with the samples. A chain of consecutive numbers can grow out of a single item ( has answer ), and gaps of two can't be crossed ( has answer ). But counting staircase runs (stretches that go up by zero or one at each step) is not enough: looks like four runs and its answer is , because the final can attach to the far to its left.
- Attach greedily. If could have been generated at all, then it could have been generated by the closest to its left that is still usable: any other valid choice can be swapped to that one. So each item has only one candidate parent to check.
- Sweep, keeping the set of possible parents. Go left to right, maintaining a set of the items that are still allowed to become parents. To add : first delete everything from the set, since with after them they can never be parents again. Then, if the largest remaining item equals , attach to it. Otherwise is a root: count it, and empty the set, because nothing before a root can be the parent of anything after it. The A1 answer is the number of roots. [KQ] [1]
- For A2, anchor on the right endpoint. Let be the A1 answer for a sequence ; A2 asks for the sum of over all subsegments. Partition the subsegments by their right endpoint, and let be the sum of over the windows ending at . When arrives, windows that still contain its parent keep the same answer, and windows that cut the parent off pay one each: , where is the parent's position from the same sweep, or for a root. [KQ] [2]
- Sum the values with 64-bit integers. The A2 code is the A1 sweep, only also recording each item's parent.
(My accepted A1 and A2 submissions.)
References
Problem-solving techniques used:
Look at examples with small variations, and watch how the answer changes as one thing changes. The whole structure of this problem came out of the last sample being instead of : one surprising example taught more than the friendly ones.
The run-counting conjecture was quick to state and quick to disprove. Once it failed, I studied the failing example, and that example carried me to the real question of how one item can generate another. Later, the sweep itself was also a conjecture — I didn't prove it in contest, I tested it on the examples and submitted.
The process is described forward (insert after ), but the input is the ending sequence, so the whole problem is about undoing it: who could have generated whom?
Sweeping is a form of this: when things are ordered, pick an ordering — left to right here — and attack the items in that order. Deciding each item's fate at the moment it arrives is what made the set data structure fall out.
The stack-like behavior of the alive set is the kind of thing you have to trace by hand. After writing out the full table for the last sample, I trusted the algorithm enough to code it.
Going back to what I'd already built: the A1 sweep was already computing every item's parent, which is exactly what the A2 recurrence needs (the lesson from Idea 2).
Learning points:
- This was a cool ad hoc problem. I don't think I'm very good at ad hoc problems, but I am getting better — and there are going to be more and more of them, so getting to a reasonable hypothesis quickly is the best strategy for me. Learn to love the ad hoc problem.
- Looking at small examples, and at how the answer changes when you add or change one thing, has been helpful again and again.
- I first tried a complicated from-scratch way of computing the parents in A2 before realizing the A1 sweep already had them (the lesson from Idea 2). Reuse what you've already built.
Topics:
- Ad hoc / constructive
- Greedy
- Dynamic programming (DP)
- Combinatorics / counting
- Data structures