学习笔记:LCA,最近公共祖先
定义
最近公共祖先(Lowest Common Ancestor),简称LCA,是算法竞赛中常用的、用以查找树上两个结点中,距离根结点最远的结点的算法。
实现
朴素算法
过程
每次找深度比较大的那个点,让它向上跳。显然在树上,这两个点最后一定会相遇,相遇的位置就是想要求的 LCA。 或者先向上调整深度较大的点,令他们深度相同,然后再共同向上跳转,最后也一定会相遇。
复杂度
由于朴素算法在预处理时要dfs整棵树,因此预处理时间复杂度为
单次查询时间复杂度同样为
倍增实现LCA
这里往下才是本篇的重点。
我们发现,在朴素算法中每次两个点向上跳的过程会耗费大量的时间,因此可以考虑对其进行优化。
那么具体该如何实现呢?显然,可以考虑使用倍增。不了解倍增原理的可以自行在oi-wiki上查阅。
我们在找LCA前先预处理好一个二维数组
过程
首先将我们要用到的数组等信息定义好:
const int N=2*1e5+10;
struct Node{
int u,v,nxt;
}edge[N];
int cnt=0;
int fa[N][32],dep[N],lg[N];
int head[N];//采用链式前向星存图
void add(int u,int v)
{
edge[++cnt].u=u;
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
void getlog(int n)
{
for(int i=1;i<=n;i++)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
}
首先依旧要dfs整棵树,预处理好dep数组以及fa数组,而其中
void dfs(int now,int fath)//now为当前结点,fath为当前结点的父结点
{
dep[now]=dep[fath]+1;//深度++;
fa[now][0]=fath;//now结点的第2^0个祖先,即第一个祖先为fath
for(int i=1;i<=lg[dep[now]];i++)
{
fa[now][i]=fa[fa[now][i-1]][i-1];//此处是倍增思想的体现,思考一下为什么
}
for(int i=head[now];i;i=edge[i].next)
{
if(edge[i].v!=fath) dfs(edge[i].v,now);//如果不重复就继续向下遍历
}
}
到这里,我们的预处理就完成了,那么该如何来寻找两个结点的LCA呢?很简单,我们只需要先将两个点跳至同一深度,再用我们预先处理好的fa数组来向上找就OK了。具体可以结合代码
int LCA(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);//为了方便处理,默认x的深度大于y
while(dep[x]>dep[y])
x=fa[x][lg[dep[x]-dep[y]]-1];//向上跳,将两个点调整至同一深度
if(x==y) return x;//若相等则直接返回
for(int k=lg[dep[x]]-1;k>=0;k--)
{
if(fa[x][k]!=fa[y][k])
{
x=fa[x][k];
y=fa[y][k];
}//将二者同时向上跳,最多跳lg[dep[x]]-1次
}
return fa[x][0];//返回此时x父节点的值,即x与y的LCA
}
贴一份完整代码
#include<bits/stdc++.h>
#define life Elaina
#define angle Exusiai
#define int long long
using namespace std;
const int N=6*1e5+10;
struct Node{
int u,v,nxt;
}edge[N*2];
int cnt=0;
int fa[N][32],dep[N],lg[N];
int head[N];
void add(int u,int v)
{
edge[++cnt].u=u;
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
void getlog(int n)
{
for(int i=1;i<=n;i++)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
}
void dfs(int now,int fath)
{
dep[now]=dep[fath]+1;
fa[now][0]=fath;
for(int i=1;i<=lg[dep[now]];i++)
{
fa[now][i]=fa[fa[now][i-1]][i-1];
}
for(int i=head[now];i;i=edge[i].nxt)
{
if(edge[i].v!=fath) dfs(edge[i].v,now);
}
}
int LCA(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
while(dep[x]>dep[y])
x=fa[x][lg[dep[x]-dep[y]]-1];
if(x==y) return x;
for(int k=lg[dep[x]]-1;k>=0;k--)
{
if(fa[x][k]!=fa[y][k])
{
x=fa[x][k];
y=fa[y][k];
}
}
return fa[x][0];
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m,s;
cin>>n>>m>>s;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
getlog(n);
dfs(s,0);
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
cout<<LCA(a,b)<<endl;
}
return 0;
}
本人的第一篇博客,轻点喷