算法与数据结构 / RMQ 与 LCA · 同一个问题的两种形态 待审核 6 页

RMQ 与 LCA · 同一个问题的两种形态

两个看起来毫不相干的问题。一个在数组上:给定不再修改的 a[0..n1]a[0..n-1],反复问「a[l..r]a[l..r] 的最小值是多少」,这是 RMQ(range minimum query)。另一个在树上:给定一棵有根树,反复问「uuvv 最深的那个公共祖先是谁」,这是 LCA(lowest common ancestor)。

本系列要说的是它们其实是一个问题。欧拉序把 LCA 变成 RMQ:把 DFS 的进入与回溯全都记下来,得到一条长 2n12n-1 的序列,两点的 LCA 就是它们首次出现位置之间深度最小的那个节点。笛卡尔树把 RMQ 变成 LCA:按「中序是原下标、堆序是值」建树,a[l..r]a[l..r] 的最小值位置就是下标 llrr 在树上的 LCA。两条归约都是线性时间的,于是任一侧的解法都能拿去解另一侧。

路线是:先在静态数组上把 sparse table 讲透——预处理 O(nlogn)O(n \log n)、查询 O(1)O(1),代价是数组不能再改;再上树,倍增给出 O(logn)O(\log n) 的在线查询,欧拉序把它压到 O(1)O(1),Tarjan 用一遍 DFS 加并查集把全部询问离线做完;最后用笛卡尔树把圈闭上,并交代 O(n)O(n)O(1)O(1) 的 ±1 分块是怎么来的。

静态数组:用空间换 O(1) 查询

sparse table 预先算好所有「长度为 2 的幂」的区间,查询时用两段长度相同的区间重叠覆盖 [l,r][l, r]。它成立的前提是聚合运算 idempotent——min(a,a)=a\min(a, a) = a,重叠算两次也无妨。求和不满足这一条,同一套查询法在它身上每一个区间都给错数。

idempotent 不是可有可无的细节

两段覆盖里的重叠格数是 22klen2 \cdot 2^k - \text{len},而 2klen<2k+12^k \le \text{len} < 2^{k+1},所以这个量恒不小于 1——两段永远至少重一格,len\text{len} 恰为 2 的幂时更是完全重合。 于是求和版不是「有时算错」而是「一个都对不了」:全 1 的 32 元数组上,528 个区间无一正确,长度为 2 的幂的那些恰好翻倍(见 sparse table §4)。可修改、可求和的那一侧属于前缀和与线段树的地盘,两者解决的不是同一类需求。

sparse table · 延伸阅读

  • Sparse Table cp-algorithms.com 建表递推、两段覆盖的查询、idempotent 与非 idempotent 两类聚合的分别处理,以及不相交稀疏表 (disjoint sparse table) 变体。
  • Range minimum query en.wikipedia.org RMQ 的问题定义与各解法的复杂度对照表,含 O(n)O(n)O(1)O(1) 一路的历史脉络。

树上:三条通向 LCA 的路

倍增预存每个节点的第 2k2^k 级祖先,查询按二进制位跳,在线且实现最短。欧拉序把树摊平成序列,LCA 随即变成 RMQ,查询降到 O(1)O(1)。Tarjan 反其道而行:先收齐全部询问,一遍 DFS 配并查集把它们一次算完,代价是不接受「边问边答」。

三种实现的分工判据

先看询问是否可以攒着。全部询问事先已知——离线可行——Tarjan 的总量随 n+qn + q 线性,常数也最小;一旦要求边问边答,它就不适用。 在线的两者之间再看 qqnn 的相对大小。qq 远小于 nn 时倍增更划算:它的预处理量是 (log2n+1)n(\lceil \log_2 n \rceil + 1) \cdot n,比欧拉序在长度 2n12n-1 的序列上建稀疏表要少(n=100000n = 100000 时实测 1700000 对 3137858)。qqnn 同量级或更大时反过来,欧拉序每次查询只做一次比较,倍增每次要读约 2logn2\log n 个表项。

LCA 的三种实现 · 延伸阅读

闭环:两个问题互为对方

笛卡尔树给出反方向的归约,RMQ 由此变成 LCA。两条归约拼在一起,就有了 O(n)O(n)O(1)O(1) 的可能:欧拉序的深度数组相邻差恒为 ±1\pm 1,这条额外性质让块内形态只有 2b12^{b-1} 种,可以全部预先打表。

归约与 O(n)–O(1) · 延伸阅读