定义
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。另一种理解方式是把T理解为一个无向无环图,而LCA(T,u,v)即u到v的最短路上深度最小的点。现在给定一个根为1的树,求某两个点的最近公共祖先。
思路:
预处理出每个点的深度再一层一层返回一直到找到相同点
deep[1]=1;
void dfs(int nw,int d){
deep[nw]=d;
for(int i=0;i<G[nw].size();i++){
int to=G[nw][i];
if(!deep[to]){
par[to]=nw;
dfs(to,d+1);
}
}
}
再返回
int LCA(int a,int b){
if(deep[a]>deep[b])swap(a,b);
while(deep[a]<deep[b])b=par[b];
while(a!=b){
a=par[a];
b=par[b];
}return a;
}
复杂度约为O(n)
但是可以使用倍增优化
以par[i][j] 记录第i个点向上返回2^j的节点
因为2^j==2*2^(j-1)
即得到par[i][j]=ar[par[i][j-1]][j-1];
于是每次dfs出每个点的par[i][0]
void dfs(int c){
for(int i=0;i<G[c].size();i++){
int t=G[c][i];
if(!d[t]){
d[t]=d[c]+1;
par[t][0]=c;
dfs(t);
}
}
}
再利用动态规划计算出par[i][j]
for(int j=1;j<S;j++)
for(int i=1;i<=n;i++)
par[i][j]=par[par[i][j-1]][j-1];
于是我们可以利用类似快速幂的思想
int up(int a,int s){
for(int i=0;i<S;i++)
if(s&(1<<i))a=par[a][i];
return a;
}
于是我们就得到了o(log2n)的算法
int LCA(int a,int b){
if(d[a]>d[b])swap(a,b);
b=up(b,d[b]-d[a]);
if(a!=b){
for(int i=S-1;i>=0;i--)
if(par[a][i]!=par[b][i])
a=par[a][i],b=par[b][i];
//找到最近的不同点
a=par[a][0];
//再上一层即为LCA
}return a;
}