最近公共祖先( L e a s t C o m m o n A n c e s t o r s \rm Least\,Common\,Ancestors LeastCommonAncestors),简记为 L C A \rm LCA LCA。顾名思义就是一棵树中的某两个节点的公共的祖先中离他们最近,即深度最大的那个。
举个例子:
上图中 8 8 8 和 6 6 6 的 LCA 就是 1 1 1。
那么怎么求 LCA 呢?
1. 向上标记法
思路十分简单。
我们现在要求
LCA(x,y)
\operatorname{LCA(x,y)}
LCA(x,y)。首先从
x
x
x 向上爬到根节点
r
t
rt
rt,经过的每一个节点都打上标记。然后从
y
y
y 向上到
r
t
rt
rt,遇到的第一个有标记的节点就是
L
C
A
LCA
LCA。
代码实现很简单,就不给了。
2. 同步前进法
求 LCA(x,y) \operatorname{LCA(x,y)} LCA(x,y) 的具体方法为:
- 先让 x x x 的深度大于或等于 y y y。这相当于数学上的一个假设: d e p ( x ) ≥ d e p ( y ) dep(x)\ge dep(y) dep(x)≥dep(y)( d e p dep dep 代表深度)
- 不停地将 x x x 往上跳,直到 x x x 和 y y y 深度相等。
- 特判:若 x = y x=y x=y 那 L C A LCA LCA 就是 x x x 了。
- 否则在保证 f a ( x ) ≠ f a ( y ) fa(x)\ne fa(y) fa(x)=fa(y)( f a fa fa 代表父亲) 的情况下 x x x 和 y y y 同时向上跳一格。
- L C A LCA LCA 就是 x x x 的父亲。
以上面的 8 8 8 和 6 6 6 为例:
- d e p ( 8 ) > d e p ( 6 ) dep(8)>dep(6) dep(8)>dep(6),无需交换。
- 8 8 8 跳到 4 4 4, d e p ( 4 ) = d e p ( 6 ) dep(4)=dep(6) dep(4)=dep(6)。
- 4 ≠ 6 4\ne6 4=6。
- f a ( 4 ) = 2 ≠ f a ( 6 ) = 3 fa(4)=2\ne fa(6)=3 fa(4)=2=fa(6)=3, 4 4 4 跳到 2 2 2, 6 6 6 跳到 3 3 3; f a ( 2 ) = 1 = f a ( 3 ) fa(2)=1=fa(3) fa(2)=1=fa(3),停止。
- LCA(8,6) = f a ( 2 ) = 1 \operatorname{LCA(8,6)}=fa(2)=1 LCA(8,6)=fa(2)=1。
在此之前还需 dfs 一遍求出 d e p dep dep 和 f a fa fa。
void dfs(int u, int father)
{
fa[u] = father;
dep[u] = dep[father] + 1; //u深度即father深度加1
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v != father)
{
dfs(v, u);
}
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
{
swap(x, y); //让x的深度大于或等于y
}
while (dep[x] > dep[y])
{
x = fa[x]; //向上跳
}
if (x == y)
{
return x; //特判
}
while (fa[x] != fa[y])
{
x = fa[x]; //同时跳
y = fa[y];
}
return fa[x]; //LCA是父亲
}
但以上两种方法时间复杂度均为 O ( n m ) \operatorname{O}(nm) O(nm)……
然后洛谷 A 了。
对于 100 % 100\% 100% 的数据, N ≤ 500000 N\le500000 N≤500000, M ≤ 500000 M\le500000 M≤500000。
数据太水了啊啊啊!!!
出数据的真良心。。。
3. 倍增
上面暴力算法显然过慢,所以我们要使用倍增算法,也是一种空间换时间的策略。
先预处理出 l g lg lg 数组,用来保存 ⌊ l o g 2 x ⌋ \left\lfloor log_2x\right\rfloor ⌊log2x⌋
for (int i = 2; i <= n; i++)
{
lg[i] = lg[i >> 1] + 1;
}
记 f a ( x ) ( i ) fa(x)(i) fa(x)(i) 为 x x x 的第 2 i 2^i 2i 级祖先,dfs 时要算 f a ( u ) ( i ) fa(u)(i) fa(u)(i)。
LCA 的第 2 2 2 步, x x x 直接向上跳 l g ( d e p ( x ) − d e p ( y ) ) lg(dep(x)-dep(y)) lg(dep(x)−dep(y)) 格。第 4 4 4 步时,我们遍历 i = l g ( d e p ( x ) ) + 1 ∼ 0 i=lg(dep(x))+1\sim0 i=lg(dep(x))+1∼0,每次 x x x 和 y y y 同时向上跳 2 i 2^i 2i 格。
void dfs(int u, int father)
{
fa[u][0] = father; //u的第1级祖先就是父亲
dep[u] = dep[father] + 1;
for (int i = 1; i <= lg[dep[u]] + 1; i++)
{
fa[u][i] = fa[fa[u][i - 1]][i - 1];
} //算fa(u)(i)
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v != father)
{
dfs(v, u);
}
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
{
swap(x, y);
}
while (dep[x] > dep[y])
{
x = fa[x][lg[dep[x] - dep[y]]];
}
if (x == y)
{
return x;
}
for (int i = lg[dep[x]] + 1; i >= 0; i--)
{
if (fa[x][i] != fa[y][i])
{
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
复杂度分析:
时间 | 空间 | |
---|---|---|
暴力 | 预处理 O ( n ) \operatorname{O}(n) O(n),每次询问 O ( n ) \operatorname{O}(n) O(n) | O ( n ) \operatorname{O}(n) O(n) |
倍增 | 预处理 O ( n log n ) \operatorname{O}(n\log n) O(nlogn),每次询问 O ( log n ) \operatorname{O}(\log n) O(logn) | O ( n log n ) \operatorname{O}(n\log n) O(nlogn) |
暴力:
倍增:
用 LCA 求树上两点间的最短距离
还是以这张图为例主要是我懒
8 8 8 和 5 5 5:
预处理出每个点到根节点的距离 d i s ( x ) dis(x) dis(x),这里因为是无权边, d i s ( x ) dis(x) dis(x) 直接就是 d e p ( x ) dep(x) dep(x) 了。
先用 d i s ( 8 ) dis(8) dis(8) 加上 d i s ( 5 ) dis(5) dis(5),此时多加了 2 2 2 遍 d i s ( 2 ) dis(2) dis(2),故 8 8 8 和 5 5 5 的距离就应该是 d i s ( 8 ) + d i s ( 5 ) − 2 × d i s ( 2 ) dis(8)+dis(5)-2\times dis(2) dis(8)+dis(5)−2×dis(2),即树上 x x x 和 y y y 两点间的最短距离为 d i s ( x ) + d i s ( y ) − 2 × d i s ( L C A ) dis(x)+dis(y)-2\times dis(LCA) dis(x)+dis(y)−2×dis(LCA)。