您的位置:首页 > 其它

倍增法求最近公共祖先(LCA)

2017-07-17 22:15 381 查看
思路分析

首先定义一个f[a][b]数组,注意因为b表示的是a向上跳2^b步所到达的点,所以b不需要开得很大,20就足够了。

然后利用邻接表存图,开一个d[]数组利用搜索遍历这张图来记录深度,然后就可以求LCA了。

求LCA时要注意:如果所求的两个点的深度不同,则先让深度大的那个节点向上跳到与另一节点同一高度,再开始让两个点同时跳同样步数。

代码注释很详细,这里就不再详述。

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,s,ans=-1,t,a,b,maxdepth,cnt,gap;
int f[10000003][20];
//f[i][j]表示从点i向上跳2^j步所能到达的点
int head[10000003],d[10000003];
struct node
{
int to;
int next;
}e[10000003];
void change(int &x,int &y)
{
int gal=x;
x=y;
y=gal;
}
void add(int x,int y)//邻接表
{
e[++cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt;
}
void dfs(int x)
{
for(int i=head[x];i;i=e[i].next)
{
t=e[i].to;
if(d[t]) continue;//如果已经记录该点深度则跳过
d[t]=d[x]+1;//当前节点的深度=其父节点的深度+1
f[t][0]=x;//表示t向上走2^0=1步到达其父节点
dfs(t);//继续遍历
}
}
void doubleinsert()
{
for(int i=1;i<=maxdepth;i++)
{
for(int j=1;j<=n;j++)
{
f[j][i]=f[f[j][i-1]][i-1];//拆分为两部分
}
}
}
void LCA(int x,int y)
{
if(d[x]<d[y]) change(x,y);
gap=d[x]-d[y];
for(int i=0;i<=maxdepth;i++)
{
if((1<<i)&gap)
//转为二进制:gap=2^i1(只要这一位是1)+2^i2+2^i3+...
{
x=f[x][i];//向上跳2^i步
}
}
if(x==y)//此时x和y在一棵树的同一侧,取深度最小的
{
ans=x;
return;
}
for(int i=maxdepth;i>=0;i--)
{
if(f[x][i]!=f[y][i])
//因为f[x][i]==f[y][i]时不一定能跳到最近的那个祖先,所以先判断如果不在同一点就跳
{
x=f[x][i];
y=f[y][i];
}
}
//此时x点已经离最近公共祖先仅差1步
ans=f[x][0];//再往上跳一步求得最近公共祖先
return;
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
maxdepth=log(n)/log(2)+1;
//maxdepth用来存储log2(n)+1,可以理解为将可能达到的最长长度用2的次方+1(保险起见)来表示
for(int i=1;i<=n-1;i++)
{
scanf("%d%d",&a,&b);
add(a,b);//无向,所以两个方向都要建边
add(b,a);
}
d[s]=1;//初始化,记根节点的深度为1
dfs(s);//遍历树,记录每个节点的深度
doubleinsert();//倍增
for(int i=1;i<=m;i++)
{
ans=-1;
scanf("%d%d",&a,&b);
LCA(a,b);//查找a和b的最近公共祖先
printf("%d\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  遍历 图论 模板类