Editorial for IOI '14 P2 - Wall
Submitting an official solution before solving the problem yourself is a bannable offence.
Overview
We'll simplify the notion of the problem as follows:
Initially, we have an array of length where the value at each index is , and we are to process queries in order. We will denote the value at index as . There are two kinds of operations:
- Minimize(l,r,t): For all indices between , the value becomes
- Maximize(l,r,t): For all indices between , the value becomes
The minimize
operation corresponds to the original remove
operation;
The maximize
operation corresponds to the original add
operation.
Trivial solution -
A trivial solution to this problem is to use an array of length to maintain the current height at each index, and for each query, perform a linear update in . This solution runs in .
Segment Tree -
One crucial observation is that for a specific position, any sequence of operations applied on it could be simplified to one minimize
operation and one maximize
operation.
For example, applying (here we omit the parameters as we consider operations solely on a specific index):
- minimize(9), minimize(8), minimize(7) ⇒ minimize(7), maximize(-inf)
- minimize(3), maximize(7), minimize(4) ⇒ minimize(4), maximize(4)
In general, this observation could be verified by simple induction.
With this observation, we can now improve the update time. We instead use a segment tree to maintain the final operations applied on an interval.
A segment tree is basically a binary tree, where each node contains the following information:
class Node {
Node *left, *right; // children nodes
int from, to; // corresponding interval [from, to]
int opmin, opmax; // min/max operation applied on interval
};
A minimize
operation would look something like:
void Node::minimize(int l, int r, int t) {
if( from==l && to==r ) { // applied on full segment
opmin = min(opmin, t);
opmax = min(opmin, opmax);
} else {
// pass down previously applied operation
left->minimize( left->from, left->to, opmin );
left->maximize( left->from, left->to, opmax );
right->minimize( right->from, right->to, opmin );
right->maximize( right->from, right->to, opmax );
// recursively apply current minimize operation
if( r<=left->to ) left->minimize( l, r, t );
else if( l>=right->from ) right->minimize( l, r, t );
else {
left->minimize( l, left->to, t );
right->minimize( right->from, r, t );
}
}
}
A single round of minimize
operations runs in , likewise for maximize
operations. Note that in the snippet above, we use the well-known lazy-update technique to maintain the operations applied at one position. i.e. the message is passed down to children nodes only when necessary, that is, when new operations are imposed on only part of the segment, so previous operations should be passed down first.
After all queries are processed, we simply scan each position and check (with complexity) the operations applied on it, obtaining the final value at the position.
The overall running time is therefore .
It is possible to optimize further so that the overall running time becomes . It is however entirely not necessary for this problem.
Comments