- BC20260025's blog
5.17信息笔记(LCA)
- 2024-5-17 16:21:35 @
最近公共祖先(LCA)
概念
定义
给定一棵有根树,对于树上的两个结点 ,称其最近公共祖先(LCA)为所有既是 的祖先,又是 的祖先的所有结点构成的集合中深度最大的一个。
求法
暴力求LCA
将深度大的点往上拉到和深度小的点同一深度,然后一起往上一步步跳,直到跳到同一点。
时间复杂度:
倍增求LCA
优化:如果能一次向上跳多步,则能大大提升效率。
思路:第一次先跳尽可能多的步数,后面每次步长折半。
定义 为 的深度, 为 的 辈祖先,即从 出发向根节点走 步能到达的结点。
容易得到:
预处理数组 的过程是递推,可以先遍历得到 ,再递归遍历计算所有值。
对于求LCA的两个结点 ,不妨设 ,否则交换两结点。
若 ,依次尝试从 向上走 步,若到达的结点比 深,则更新 为 。
一直到 ,若 ,则 ;否则,依次尝试从 向上走 步,若 ,则更新 为 , 为 。
最后结果为 。
预处理时间复杂度:
总时间复杂度:
void pre_deal(int u,int fa){
dep[u]=dep[fa]+1;
for(int i=0;i<logn;++i)
f[u][i+1]=f[f[u][i]][i];
for(int e=head[u];e;e=next[e]){
int v=go[e];
if(v==fa) continue;
f[v][0]=u;
pre_deal(v,u);
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]) return LCA(y,x);
for(int i=logn;i>=0;--i){
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
}
for(int i=logn;i>=0;--i)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
例题
A. 点的距离
找到 的 ,距离就是 。
# include<iostream>
# include<cmath>
using namespace std;
const int N=1e5+5,LN=20;
int n,x,y,q,logn;
int dep[N],f[N][LN];
int cnt,head[N],go[2*N],nxt[2*N]; //两倍边
void add(int x,int y){
go[++cnt]=y;
nxt[cnt]=head[x];
head[x]=cnt;
}
void pre_deal(int u,int fa){
f[u][0]=fa;
dep[u]=dep[fa]+1;
for(int i=1;i<=logn;++i)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=nxt[i]){
int v=go[i];
if(v==fa) continue;
pre_deal(v,u);
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]) return LCA(y,x);
for(int i=logn;i>=0;--i)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=logn;i>=0;--i)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int dis(int x,int y){
return dep[x]+dep[y]-2*dep[LCA(x,y)];
}
int main(){
cin>>n;
logn=floor(log2(n));
for(int i=1;i<n;++i){
cin>>x>>y;
add(x,y);
add(y,x);
}
pre_deal(1,0);
cin>>q;
while(q--){
cin>>x>>y;
cout<<dis(x,y)<<endl;
}
return 0;
}
B. 暗的解锁
容易得到“主要边”构成一棵树。把一条附加边 添加到图上,会与树上 之间的路径构成环。也就是说,如果第一步选择切断 之间的路径上的某条边,那么第二步就必须切断边 ,才能让图不连通。
因此我们可以称每条附加边 都把树上 之间的路径上每条边都覆盖一次。我们只需统计每条主要边被覆盖了多少次。
采用“树上差分”的思路,对于每条附加边 ,令两结点的权值 , 的权值 ,那么对于每个结点 ,其所有子树上的结点的权值之和就是边 的覆盖次数。
时间复杂度:
G. 聚会
先证明一个引理:对于有根树上的三个结点 ,记 ,则 中至少有两个相同。
引理的证明:假设 两两不同。注意到 和 都是 的祖先,那么 和 中有一个是另一个的祖先。 和 , 和 同理。
不妨设 是 的祖先, 是 的祖先,于是 同时是 的祖先,与 的最近性矛盾!
回到原题,对于每个询问 ,如果:
- ,输出“ ”。
- 中有两个相等,不妨设 ,则输出“ ”。
- 两两不相等,记 。
- 若 ,则输出“ ”;
- 否则由引理可设 ,则输出“ ”。