LCA学习笔记
定义:在一棵树中,两个节点的最近公共祖先。
1.暴力求法
处理出每个点的深度,先把深度较深的一个点沿着父节点方向一直走到与另一个点相同的深度,如果此时两个点不同,那么两个点一起向上跳(代码实现过于简单,这里不过多赘述)
2.倍增优化暴力
注意到我们在暴力求法中,点是一步一步向上跳的,那我们有没有办法加快这个过程呢?这个时候就要使用倍增了,预处理出每个节点的深度dep,和它的 2^i 级祖先,那么我们可以在跳的时候一次进行多步跳跃
预处理代码(也可以写在一起)
void dfs(int u,int fa){
dep[u]=dep[fa]+1,f[u][0]=fa;
for(int i=head[u];i;i=ne[i]){
int v=to[i];
if(v==fa) continue ;
dfs(v,u);
}
}
void init(){
for(int j=1;j<=20;++j)
for(int i=1;i<=n;++i)
f[i][j]=f[f[i][j-1]][j-1];
}
查询LCA:
这个要注意:在枚举祖先时一定要倒序枚举i,因为要从里根近的一步一步走到深度更大的,剩下的根据暴力的思想不难写出:
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;--i){
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
}
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
3.RMQ
首先把这棵树转成欧拉序列,用pos[i]记录树上i点第一次出现在欧拉序列中的位置,由于欧拉序列的性质,两个点的lca一定时两个点在序列中出现的区间中dep最小的点所对应的点,(可以结合欧拉序列理性感受一下),那么树上求lca问题就可以转化成序列RMQ问题了
初始化:
int pos[N];//树上节点在序列第一次出现中的编号
int a[N];//欧拉序列
int dep[N];
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
pos[u]=++tot;
a[++tot]=dep[u];
for(int i=head[u];i;i=ne[i]){
int v=to[i];
if(v==fa) continue ;
dfs(v,u);
}
a[++tot]=dep[u];
}
查询
(静态区间最小值,不过多赘述)
4.树链剖分
初始化
常规进行重链剖分或者长链剖分(只求lca的话建议长链剖分),不贴代码
查询lca
根据树剖已经拆树成链,也和倍增类似可以一次跳多个
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]])
x=f[top[x]];
else
y=f[top[y]];
}
if(dep[u]>dep[v]) return v;
return u;
}