Algorithm Search⌘K

Proofs: Offline RMQ in linear time with union-find (Arpa's trick)

These are the human proofs of Offline RMQ in linear time with union-find (Arpa's trick), at full length.

Arpa's trick returns the leftmost minimum of each range full

This proof shows: correctness, termination.

Write root(t)\mathrm{root}(t) for the root of the tree of tt in the forest pp. Positions are compared as integers. We prove an invariant of the sweep and derive the answers from it.

Invariant (A). After line L7 of step ii, let the stack be s1<s2<⋯<sk=is_1 < s_2 < \cdots < s_k = i, and put s0=−1s_0 = -1. Then:

  1. a[s1]≤a[s2]≤⋯≤a[sk]a[s_1] \le a[s_2] \le \cdots \le a[s_k];
  2. for 0≤t≤i0 \le t \le i, root(t)=sj\mathrm{root}(t) = s_j for the least jj with sj≥ts_j \ge t, and positions t>it > i are their own roots;
  3. for each jj and each tt with sj−1<t≤sjs_{j-1} < t \le s_j, every u∈[t,sj)u \in [t, s_j) has a[u]>a[sj]a[u] > a[s_j].

So part 3 says that sjs_j is the unique leftmost minimum of a[t..sj]a[t..s_j]: every earlier position of the interval has a strictly larger value.

We also use that every parent pointer goes to a larger position, p[t]≥tp[t] \ge t, with equality only at roots. Line L6 sets p[j]←ip[j] \leftarrow i with j<ij < i, and path compression sets p[x]←rp[x] \leftarrow r with rr the root reached from xx, which is larger than xx when xx is not a root. So FIND climbs strictly increasing positions and stops after at most nn steps. This is the termination of each FIND; the sweep has nn steps, each position is pushed once and popped at most once, and there are qq finds, so the algorithm terminates.

Path compression keeps the roots. A FIND sets p[x]p[x] to root(x)\mathrm{root}(x) for nodes on the path to that root, so each node keeps its root. So the statements of (A), which speak only of roots, the stack and the array, are not changed by the finds of lines L8 and L9. We can ignore compression below.

Base. Before step 0 the stack is empty and every position is a root. In step 0 nothing is popped and 0 is pushed. Then k=1k = 1, s1=0s_1 = 0, part 1 is empty, part 2 holds since root(0)=0\mathrm{root}(0) = 0, and part 3 holds for t=0t = 0, because the range [0,0)[0, 0) is empty.

Step. Assume (A) after step i−1i - 1, with the stack s1<⋯<sk=i−1s_1 < \cdots < s_k = i - 1. In step ii, the loop of lines L5 and L6 pops sk,sk−1,…,sh+1s_k, s_{k-1}, \ldots, s_{h+1}, the positions with a[sj]>a[i]a[s_j] > a[i], and stops at shs_h with a[sh]≤a[i]a[s_h] \le a[i], or h=0h = 0 when the stack runs out. By part 1, the popped positions are exactly the top ones with values larger than a[i]a[i], because the values on the stack do not fall from bottom to top. Each popped sjs_j, which is a root, gets the parent ii, which is a root. Line L7 pushes ii. The new stack is s1<⋯<sh<is_1 < \cdots < s_h < i.

Part 1. a[sh]≤a[i]a[s_h] \le a[i] by the stop condition, and the lower part is unchanged.

Part 2. For t≤sht \le s_h, the root of tt is the old one, sjs_j with j≤hj \le h, which is still on the stack and is the least stack position ≥t\ge t. For sh<t≤i−1s_h < t \le i - 1, the old root was some popped sjs_j with j>hj > h, which now has the parent ii, a root; so root(t)=i\mathrm{root}(t) = i, the least stack position ≥t\ge t. For t=it = i, root(i)=i\mathrm{root}(i) = i. Positions after ii are untouched.

Part 3. For the intervals (sj−1,sj](s_{j-1}, s_j] with j≤hj \le h, nothing changed. For the new last interval (sh,i](s_h, i], take tt in it. If t=it = i the claim is trivial. Otherwise tt lies in an old interval (sj−1,sj](s_{j-1}, s_j] with h<j≤kh < j \le k. By part 3 for the old state, every u∈[t,sj]u \in [t, s_j] has a[u]≥a[sj]>a[i]a[u] \ge a[s_j] > a[i], because sjs_j was popped. For uu in a later old interval (sl−1,sl](s_{l-1}, s_l] with j<l≤kj < l \le k, the same argument applied to t′=sl−1+1t' = s_{l-1} + 1 gives a[u]≥a[sl]>a[i]a[u] \ge a[s_l] > a[i]. So every u∈[t,i)u \in [t, i) has a[u]>a[i]a[u] > a[i], which is part 3 for the new interval.

So (A) holds after step ii.

The answers. At line L9 of step ii, take a query (L,i)(L, i). By part 2, FIND(L)=sj\mathrm{FIND}(L) = s_j, the least stack position ≥L\ge L, so sj−1<L≤sjs_{j-1} < L \le s_j. By part 3 with t=Lt = L, every u∈[L,sj)u \in [L, s_j) has a[u]>a[sj]a[u] > a[s_j]. For u∈(sj,i]u \in (s_j, i], uu lies in some interval (sl−1,sl](s_{l-1}, s_l] with l>jl > j, and by part 3 and part 1, a[u]≥a[sl]≥a[sj]a[u] \ge a[s_l] \ge a[s_j]. So a[sj]a[s_j] is the minimum of a[L..i]a[L..i], and no position before sjs_j in the range reaches it. So sjs_j is the leftmost minimum, which is the required answer. ■\blacksquare

Back to Offline RMQ in linear time with union-find (Arpa's trick)