专栏文章

浅谈 LCA

算法·理论参与者 8已保存评论 9

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
9 条
当前快照
1 份
快照标识符
@miqx4qvu
此快照首次捕获于
2025/12/04 12:11
3 个月前
此快照最后确认于
2025/12/04 12:11
3 个月前
查看原文
一些定义:faifa_iii 的父亲,depidep_iii 的深度,dfnidfn_iii 的深度,eulerieuler_iii 的欧拉序,minf{S}\min^{f} \{S\}xS,fx\forall x \in S, f_x 最小的元素 xxmaxf\max^f 同理。

0. 定义

节点 uu 与节点 vv 的 LCA(最近公共祖先) 的定义是深度最深的节点 ww 满足 ww 同时是 uu 的祖先与 vv 的祖先,记为 lca(u,v)=w\operatorname{lca}(u,v)=w

1. 求法

LCA 的求法还是挺多的,这里简要介绍几种比较有实用价值的方法。
看起来很多资料都谈到了 tarjan 求 LCA 的方法,但个人认为 tarjan 求 LCA 并不比倍增/DFS 序/欧拉序法复杂度显著优,且需要数据可以离线,而对于很多题目,即使可以离线 LCA 询问,也往往会增加代码难度,作者也没有找到有意义的推广,也并不易于理解。因此没有提及。
此处用 O(A)O(B)O(A) \sim O(B) 代表该做法需要用 O(A)O(A) 的复杂度预处理,之后用 O(B)O(B) 的复杂度处理每次询问。

1.1 朴素法

这个方法其实实用价值不那么大,但是是很多方法的基础。朴素法求 LCA 共分为两步,复杂度为 O(N)O(N)O(N) \sim O(N)
  1. 不断让深度较深的节点跳父亲,直到两个节点的深度相同。
  2. 让两个节点同时跳父亲,直到两个节点相同,即为 LCA。
另一种实现是我们将从 xx 到根的路径染色,之后找到从 yy 到根的路径上深度最深的有颜色的节点即可。

1.2 倍增法

倍增法较为好写,复杂度在大部分情况下也是足够的,复杂度为 O(NlogN)O(logN)O(N \log N) \sim O(\log N)。下面默认 xx 为深度较深的节点,yy 为深度较浅的节点。预处理 fi,jf_{i,j} 代表 ii 的第 2j2^j 个父亲。
对于朴素法的第一步,1ilog2N\forall 1 \leq i \leq \log_2 N,若 depfx,idepydep_{f_{x,i}} \leq dep_y,则令 x=fx,ix=f_{x,i} 即可,倒序循环,易证对于每个 ii 至多只能这样跳一次。(否则会在 i+1i+1 的时候跳,当 i=log2Ni=\log_2N2i>N2^i \gt N,所以也不可能出现)
对于第二步 i\forall ifx,ify,if_{x,i} \neq f_{y,i},则令 x=fx,i,y=fy,ix=f_{x,i},y=f_{y,i} 即可,最终的 LCA 即为 fx,0f_{x,0},需要特判若第一步后 x=yx=y 则 LCA 为 xx
应对一些卡空间的毒瘤题目时,可以将 fi,jf_{i,j} 改为 ii 的第 wjw^j 个父亲,其中 ww 是一个较小的正整数,注意可以跳多次父亲,时间复杂度为 O(NwlogwN)O(wlogwN)O(N w \log_w N) \sim O(w \log_w N),空间复杂度为 O(NlogwN)O(N\log_wN)

1.3 DFS 序法

DFS 序法的优势是复杂度较为优秀,常规实现的复杂度为 O(NlogN)O(1)O(N \log N) \sim O(1),部分优秀实现可以到达 O(N)O(1)O(N) \sim O(1)。下面令 xx 为 DFS 序较小的节点,yy 为 DFS 序较大的节点,wwlca(x,y)\operatorname{lca}(x,y)。分情况讨论:
  1. xxyy 互不为祖先关系,这种情况下我们 DFS 的顺序一定是先从 ww 访问到 uu,再回到 ww,再从 ww 访问 uu,因此 dfnxdfnidfnz\forall dfn_x \leq dfn_i \leq dfn_ziiww 的子树中(不含 ww)。而因为 wwuu 的路径与 wwvv 的路径不交,所以 dfnxdfn_xdfnydfn_y 中一定恰好有两个 ww 的儿子节点,我们取所有 dfnxdfnidfnydfn_x \leq dfn_i \leq dfn_y 中深度最浅的节点 ii 的父亲,即为 LCA ww
  2. xxyy 的祖先, 考虑①中的结论为什么不对,因为深度最浅的节点 uu 的父亲是 faufa_u,所以我们会错误的算出 w=fauw=fa_u 而不是 w=uw=u,我们考虑对式子稍作修改,让深度最浅的节点变为 xxyy 的路径上深度次浅的节点,容易发现只需要把式子变形为 dfnx<dfnidfnydfn_x \lt dfn_i \leq dfn_y 即可。
  3. x=yx=y 特判即可,w=xw=x
情况①与情况②中两个不同的式子看起来很不优雅,考虑统一两个式子,容易发现在 xx 不为 yy 的祖先时,即使我们令 xx 为 DFS 序恰好为 dfnx+1dfn_x+1 的节点,我们仍然会经过至少一个 ww 的子节点,且仍然只会在 ww 的不含其自身的子树里行动,所以对于情况①可以直接用情况②的式子。
但这样我们需要同时维护 depdepdfndfn,看起来还是比较麻烦,但因为父亲的 dfndfn 最小的节点的父亲一定还是 wwdfnx>dfny,ysubtree(x),yxdfn_x \gt dfn_y, \forall y \in \operatorname{subtree}(x), y \neq x)。我们直接用一个 ST 表,底层维护父亲编号,高层维护 dfndfn 最小的节点的父亲即可(sti,jst_{i,j} 代表 mindfn(fax,i<dfnxi+2j)\min^{dfn}(fa_x, \forall i \lt dfn_x \leq i+2^j))。就达到了预期的复杂度。用四毛子可以达到更优的复杂度,但非常麻烦。

1.4 欧拉序法

欧拉序法的复杂度与 DFS 序法一样优,但在部分题目(e.g. 区间 LCA)中需要同时记录欧拉序与 DFS 序,较为麻烦。令 pip_i 为编号为 ii 的节点在欧拉序中第一次出现的位置,x=minp(x,y),y=maxp(x,y)x=\min^{p}(x,y),y=\max^p(x,y)
结论:lca(x,y)=minp{i,ieulerpxpy}\operatorname{lca}(x,y) = \min^{p} \{i, i \in euler_{p_x \cdots p_y}\}。证明为 eulerpxpyeuler_{p_x \cdots p_y} 一定包含回溯时产生的 lca(x,y)\operatorname{lca}(x,y),且一定不包含 falca(x,y)fa_{\operatorname{lca}(x,y)}(因为只在 lca(x,y)\operatorname{lca}(x,y) 的子树中)。
直接用 ST 表或四毛子维护即可,复杂度为 O(NlogN)O(1)O(N \log N) \sim O(1)O(N)O(1)O(N) \sim O(1)

1.5 树链剖分

在树上不断跳重链,直到两个节点处于同一个重链即可。复杂度为 O(N)O(logN)O(N) \sim O(\log N) 且常数较小。

2. 性质

定义 lca(S)\operatorname{lca}(S) 为集合 SS 中所有元素的最近公共祖先。
  1. lca(S)=lca(mindfn(S),maxdfn(S))\operatorname{lca}(S)=\operatorname{lca}(\min^{dfn}(S), \max^{dfn}(S)),证明不难通过 1.3 中的结论得到,可用于优化子集的 LCA。
  2. lca(ST)=lca(lca(S),lca(T))\operatorname{lca}(S \cup T)=\operatorname{lca}(\operatorname{lca}(S), \operatorname{lca}(T)),证明显然,是很多性质的基础。
  3. xxyy 在树上的简单路径可以分为 lca(x,y)\operatorname{lca(x,y)}xx 的链与 lca(x,y)\operatorname{lca(x,y)}yy 的链,证明:显然一定会经过 lca(x,y)\operatorname{lca}(x,y),而如果经过 falca(x,y)fa_{\operatorname{lca}(x,y)},则不为简单路径。
  4. lca(xy)=mindep(lca(x,x+1),lca(x+1,x+2)lca(y1,y))\operatorname{lca}(x \cdots y)=\min^{dep}(\operatorname{lca}(x, x+1), \operatorname{lca}(x+1, x+2) \cdots \operatorname{lca}(y-1, y)),证明基于虚树。
  5. 若树上 x,yx,y 的简单路径与 a,ba,b 的简单路径相交,则 lca(x,y)\operatorname{lca}(x,y)a,ba,b 的简单路径上(或反过来)。

3. 应用

3.1 关于 LCA 本身

3.1.1 [LNOI2014] LCA

3.1.1.1 题面

有一个以 00 为根的有 nn 个节点的有根树,mm 次询问,每次给定 l,r,xl,r,xi=lrdeplca(i,x)\sum_{i=l}^r dep_{\operatorname{lca}(i,x)}

3.1.1.2 题解

考虑运用朴素法求 LCA 中的思路 ②,我们将从根到 xx 的路径上的所有节点染色,容易发现 deplca(x,y)=dep_{\operatorname{lca}(x,y)}=yy 到根的路径上被染色的节点数量,依此可以做到 O(nm)O(nm),注意到我们都是路径修改与查询,考虑树链剖分与线段树。为了避免烦人的零下标,可以将所有节点编号 +1+1 后处理。
但这样复杂度还是不够,考虑差分,将问题转化为把 yy 到根染色,之后查询 xx 到根的染色节点数量。将 [l,r][l,r] 拆成两个询问 [1,r][1,r][1,l1][1,l-1],分别赋 111-1 的权值,按右端点排序,每当我们遇到一个区间就在当前的线段树上查询一次,否则给当前点到根的路径 +1+1 即可。
[GXOI/GZOI2019] 旧词 也和这题差不多,我们可以发现,我们加的一实际上是 valivalfaival_i-val_{fa_i}(而本题中 vali=depival_i=dep_i,旧词中 vali=depikval_i=dep_i^k),我们预处理出所有的 wi=jivaljvalfajw_i=\sum_{j \leq i} val_j-val_{fa_j} 的值,之后对于下传标记和线段树更新时,我们不再把乘上 rl+1r-l+1,转而乘上 wrwl1w_r-w_{l-1} 即可,且旧词不需要差分。

3.1.2 CF1062E Company

3.1.2.1 题面

给定一棵树,若干次询问,每次询问给定 l,rl,r 求编号为 lrl \sim r 中的节点任意删去一个后剩余节点 LCA 的深度的最大值。

3.1.2.2 题解

基于 LCA 的性质 ①,可以发现区间 LCA 只和 DFS 序最小与最大的节点有关,只需要分别尝试删去 DFS 序最小的最大的节点,设删去 pp,依据结论②我们可以分别求 [l,p1][l,p-1] 的 LCA 与 [p+1,r][p+1, r] 的 LCA,之后求这两个 LCA 的 LCA 即可,注意本题中根节点的深度为 00

3.1.3 [NOIP2024] 树上查询

3.1.3.1 题面

给定一颗以 11 为根的树,qq 组询问,每次询问给定 l,r,xl,r,x,之后求 [l,r][l,r] 中任意长度大于等于 xx 的连续子区间的 LCA 深度的最大值。

3.2 LCA 实现树上路径问题

LCA 的此类应用大多可以被树链剖分取代,此类问题常常需要用到性质③。
树链剖分大多具有更低的思维难度与更广泛的适用性,但代价是更高的代码难度。

3.2.1 P3398 仓鼠找 sugar

3.2.1.1 题面

给定一个树,多组询问,对于每个询问给定 a,b,c,da,b,c,daabb 的简单路径与 ccdd 的简单路径是否有交集。

3.2.1.2 题解

基于 LCA 的性质⑤可以将问题转化为判断 aabb 的路径是否经过 lca(c,d)\operatorname{lca}(c,d)ccdd 同理),之后利用性质③倍增判断即可,具体的,分别将 aabb 跳祖先跳到深度与 lca(c,d)\operatorname{lca}(c,d) 相同的节点 a,ba',b'a=lca(c,d)a'=\operatorname{lca}(c,d)b=lca(c,d)b'=\operatorname{lca}(c,d),就说明 aabb 的路径经过 lca(c,d)\operatorname{lca}(c,d)
作为对照,本题的树剖解法是将树上 aabb 的路径 +1+1,查询树上 ccdd 的路径和是否为 00,之后将 aabb 的路径减一。

3.2.2 [USACO15DEC] Max Flow P

3.2.2.1 题面

给定一颗树,多组询问,每次给定 u,vu,v,将树上 u,vu,v 的简单路径经过的节点权值 +1+1,求最终权值。

3.2.2.2 题解

基于性质③,我们维护一个树上差分,将操作拆成 u,vu,vlca(u,v)\operatorname{lca}(u,v) 链加,对照序列差分定义树上差分,令 difi=vivj[jchildren(i)]dif_i=v_i-\sum v_{j}[j \in \operatorname{children}(i)](其中 vjv_j 代表实际的权值),对于每个操作给 difudifu+1,difvdifv+1,diflca(x,y)diflca(x,y)2dif_u \gets dif_u+1,dif_v \gets dif_v+1,dif_{\operatorname{lca}(x,y)} \gets dif_{\operatorname{lca}(x,y)}-2,之后树上前缀和即可。
作为对照,本题的树剖解法是直接将 uuvv 的路径 +1+1,最后统计一次。

评论

9 条评论,欢迎与作者交流。

正在加载评论...