算法介绍
什么是LCA?举个简单的例子,想象一下你的家族族谱,你和你的表哥想找到你们最近的公共祖先,可能是你的爷爷奶奶,也可能是更早的祖先,而最近公共祖先就是距离你们最近的那个共同祖辈,LCA算法就是在一棵树中,找到两个节点最近的公共祖先节点,对于树中的两个节点u和v,他们的lca是满足以下三个条件的节点w:
1. w是u的祖先;
2. w是v的祖先;
3. 在满足前两个条件的所有节点中,w的深度最大(即最深的公共祖先)
核心算法
基本思路
1. 分别找到两个节点到根节点的路径
2. 比较两条路径,找到最后一个相同的节点
具体步骤
1、图的处理,先用dfs遍历图,存下各个点的深度,以及各个点倍增跳转数组
```Objective-C++
void dfs(int u,int fa) // u为当前节点,fa为父节点
{
f[u][0] = fa;
d[u] = d[fa]+1;
st[u]=g++;
for(int i=1;(1<<i)<=d[u];i++)
f[u][i] = f[f[u][i-1]][i-1]; //预处理f数组
//存u的倍增数组
for(int i=head[u];i!=-1;i=e[i].next){
int v = e[i].to;
if(v!=fa)
dfs(v,u);//遍历u的孩子,继续进入dfs预处理倍增数组
}
}
倍增法:能跳远就跳远,能跳近就跳近;
预处理:对每个节点预计算它的
2
0
2^0
20、
2
1
2^1
21、
2
2
2^2
22……级的祖先,便于查询时利用二进制思想,快速跳到LCA。
2、求u和v的lca,首先将u和v跳转到同一个深度,特判一下后,开始找公共祖先
```Objective-C++
int lca(int x,int y){
if(d[x]>d[y]) //令y为更深节点
swap(x,y);
for(int i=20;i>=0;i–){
if(d[y]-(1<<i)>=d[x])
y=f[y][i];//改变y 使y与x在同一深度
}
if(x==y) return y;//特判,此时若x=y,则说明x恰好是y的祖先——>特殊情况
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];//上跳1步相同,即最近的公共祖先
}
步骤解释
为什么要先对齐深度呢?
假设两个人站在不同的楼层的楼梯上,要找到他们最近的汇合点,首先要让他们站到同一楼层;
为什么要从大到小遍历i?
就像我们去超市买东西,用不同面额的钱币找零一样,优先使用大面额(大步跳跃),剩余的用小面额来补齐(小步调整)。
为什么最后返回 f[u][0] ?
经过第二步之后,u和v已经非常接近LCA了,那么他们的直接父节点就是我们要找的LCA。
模板例题
P3379 【模板】最近公共祖先(LCA)
直接套我上面所给出的模板即可啦~ 不清楚怎么套模板的可以参考下面AC代码示例,我会为大家写详细注释嘟~
#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 500000+7, MAX_M = 500000+7;
int n,m,s;
int d[MAX_N]; //保存节点深度
int f[MAX_N][21]; //保存节点的不同倍数的父节点
int g=1,st[MAX_N];// dfs序||时间戳
int head[MAX_N], cnt=-1;//cnt记录边的数目
struct Edge{
int to , next;
}e[MAX_N*2];
void add(int u,int v){
e[++cnt].to = v;//++cnt先将cnt的值加1,得到一个新的编号,e[++cnt].to=v把新边的重点设为v
e[cnt].next = head[u];//head[u]保存的是从顶点u出发的当前第一条边的编号
//e[cnt].next=head[u]让新添加的边指向原来从顶点u出发的第一条边,意味着新边成为了从顶点u出发的边链表中的新头部。
head[u] = cnt;//head[u]更新为新边的编号,也就是让新边成为从顶点u出发的第一条边
}//头插法存图
void dfs(int u,int fa) // u为当前节点,fa为父节点
{
f[u][0] = fa;//u向上跳2^0=1步时就是其父节点
d[u] = d[fa]+1;//该节点的深度是其父节点的深度加一
st[u]=g++;
for(int i=1;(1<<i)<=d[u];i++)
f[u][i] = f[f[u][i-1]][i-1];
//预处理f数组,即u倍增数组
for(int i=head[u];i!=-1;i=e[i].next){
int v = e[i].to;
if(v!=fa)//遍历图,遍历当前节点的子节点,继续进入dfs,
dfs(v,u);//直至所有节点的倍增数组全部处理完
}
}
int lca(int x,int y){
if(d[x]>d[y]) //令y为更深节点
swap(x,y);
for(int i=20;i>=0;i–){
if(d[y]-(1<<i)>=d[x])
y=f[y][i];//改变y 使y与x在同一深度
}
if(x==y) return y;//特判,此时若x=y,则说明x恰好是y的祖先——>特殊情况
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];
}
int main()
{
memset(head,-1,sizeof(head));
int a,b;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
add(b,a); //无向图,要加两次
}
dfs(s,0);//进入dfs进行预处理
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
printf("%d\\n",lca(a,b));
}
return 0;
}
评论前必须登录!
注册