云计算百科
云计算领域专业知识百科平台

【小白学算法】LCA倍增法求公共祖先超详细解析+例题[洛谷]P3379最近公共祖先(LCA)

算法介绍

什么是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;
}

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【小白学算法】LCA倍增法求公共祖先超详细解析+例题[洛谷]P3379最近公共祖先(LCA)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!