浅谈 LCA
定义
祖先:在一个有根树中,一个点到根节点的路径中,经过的所有点都成为它的祖先(自己也是自己的祖先)。
最近公共祖先:给出两个点,在它们公共的祖先中,离两个点最近的就是这两个点的最近公共祖先,即 LCA。

在这个图中,两个红色点的 LCA 就是绿色点。
解法
向上标记法
从一个点向上走,每走到一个点标记一下,再从另一个点走,第一次走到被标记的那个点就是 LCA。
时间复杂度:\(O(n)\)
倍增法
我们先预处理每个点向上走 \(2^k(k\in \Z)\) 步后到达的点是谁。设 \(f(i,j)\) 为从 \(i\) 开始向上走 \(2^j\) 步到达的点,\(j\in[0,\lfloor \log n \rfloor]\)。由于跳 \(2^k\) 步等价于跳 \(2^{k-1}\) 步再跳 \(2^{k-1}\) 步,所以我们可以递推预处理。所以可以得到递推式:\(f(i,j)=f[f(i,j-1),j-1]\)。
设 \(\text{dep}(i)\) 表示 \(i\) 这个点的深度,根节点的深度为 \(1\),那么每个点的深度就是到根节点的距离加一。
特别地,如果从 \(i\) 开始跳 \(2^k\) 步会跳出根节点,那么规定 \(f(i,k)=0\)。同时规定 \(\text{dep}(0)=0\)。
两个点 \(x,y\) ,下面是求 LCA 的步骤:
-
如果 \(\text{dep}(x)<\text{dep}(y)\),那么将 \(x\) 与 \(y\) 的值交换。
-
先将两个点跳到同一层,即将较深的那个点跳到较浅的那一层。这基于二进制拼凑,如果 \(\text{dep}(f(x,k))<\text{dep}(y)\),就不跳,否则就跳,直到 \(\text{dep}(x)=\text{dep}(y)\)。
-
两个点同时往上跳,直到跳到 LCA 的下一层,也就是跳到 LCA 的儿子。因为如果跳到 LCA 的话,我们无法判断这是最近公共祖先还是只是公共祖先。直白一点,如果程序中,\(f(x,k)=f(y,k)\),只能说明这是祖先,但如果 \(f(x,k)\ne f(y,k)\),那么这个点一定在 LCA 的下面,所以一定可以跳,那就说明按照这个程序执行后,这两个点一定是 LCA 的儿子。
-
选一个点再跳一步,那么这个点就是 LCA。
预处理时间复杂度:\(O(n\log n)\)。
查询时间复杂度:\(O(\log n)\)。
例题
题目。
模板题,可以当模板代码记:
#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=40010,M=100010;
int n,m;
int h[N],e[M],ne[M],idx;
int dep[N],fa[N][16];
int q[N];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void bfs(int root){
mems(dep,0x3f);
dep[0]=0;
dep[root]=1;
int hh=0,tt=0;
q[0]=root;
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dep[j]>dep[t]+1){
dep[j]=dep[t]+1;
q[++tt]=j;
fa[j][0]=t;
for(int k=1;k<=15;k++) fa[j][k]=fa[fa[j][k-1]][k-1];
}
}
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int k=15;k>=0;k--)
if(dep[fa[x][k]]>=dep[y]) x=fa[x][k];
if(x==y) return x;
for(int k=15;k>=0;k--)
if(fa[x][k]!=fa[y][k]){
x=fa[x][k];
y=fa[y][k];
}
return fa[x][0];
}
int main(){
cin>>n;
int root=0;//根节点
mems(h,-1);
for(int i=0;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
if(b==-1) root=a;
else{
add(a,b);
add(b,a);
}
}
bfs(root);//预处理
cin>>m;
while(m--){
int a,b;
scanf("%d%d",&a,&b);
int p=lca(a,b);
if(p==a) puts("1");
else if(p==b) puts("2");
else puts("0");
}
return 0;
}
附洛谷模板题代码:
#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=500010,M=1000010;
int n,m,root;
int h[N],e[M],ne[M],idx;
int dep[N],fa[N][21];
int q[N];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void bfs(int root){
mems(dep,0x3f);
dep[0]=0;
dep[root]=1;
q[0]=root;
int hh=0,tt=0;
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dep[j]>dep[t]+1){
dep[j]=dep[t]+1;
q[++tt]=j;
fa[j][0]=t;
for(int k=1;k<=20;k++) fa[j][k]=fa[fa[j][k-1]][k-1];
}
}
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int k=20;k>=0;k--)
if(dep[fa[x][k]]>=dep[y]) x=fa[x][k];
if(x==y) return x;
for(int k=20;k>=0;k--)
if(fa[x][k]!=fa[y][k]){
x=fa[x][k];
y=fa[y][k];
}
return fa[x][0];
}
int main(){
mems(h,-1);
cin>>n>>m>>root;
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
bfs(root);
while(m--){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
return 0;
}

浙公网安备 33010602011771号