如何在 DAG 中找多个点的 LCA ?
关注者
98被浏览
26,084登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏
什么是lca?
他是树上的最近公共祖先。首先砍一棵树:
整棵树的祖先为1号。举个例子,六号的祖先为三号,八号的祖先是四号,五号的祖先是二号......
那么,最近公共祖先就是两个树上的点与他们距离最短的一个祖先。比如,七八号的lca就是四号,二号和三号的为一号。特别的,六号和九号是六号。
那么,怎么求取最近公共祖先那?我们可以先来考虑暴力的思路。就是两个点一起往上搜索,一只搜到第一个重合的点为止。但是,这样太慢了。我们怎么更快地求取呢?
我们有一个思路就是倍增法。我们可以按照二的一次方一直跳。但注意,我们这次是从上往下找。如果跳过了,我们可以把它减小。
拿5为例:
从小向大跳:1->2->4 但很显然,这不是五,所以我们还要回推。
从大向小跳,4->1,就得到了。
还有,5的二进制位101,从高位向低位填,如果填了比原数大了,那就不填。
我们之后要把两点提到同一深度之后再开始跳。
要像这样实现,我们还需要求取二的各次方的父亲是多少。
我们这里要知道,一个点n的二的n次方的祖先为n的二的(i-1)次方的祖先的二的(i-1)次方的祖先。
给出代码:
void dfs(int now,int fa){
f[now][0]=fa,dep[now]=dep[fa]+1;
for(int i=1;i<=20;i++)f[now][i]=f[f[now][i-1]][i-1];
for(int i=head[now];i;i=e[i].nxt)if(e[i].t!=fa)dfs(e[i].t,now);
}
我们跳完之后,要让这个点跳到lca的儿子,之后返回他的父亲节点。否则他就会出现判断错误。
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
int t;
int nxt;
};
node e[1000005];
int head[1000005];
int top;
int loog[1000005];
void add(int x,int y){
e[++top].t=y,e[top].nxt=head[x],head[x]=top;
}
int dep[1000005];
int f[10000005][21];
void dfs(int now,int fa){
f[now][0]=fa,dep[now]=dep[fa]+1;
for(int i=1;i<=20;i++)f[now][i]=f[f[now][i-1]][i-1];
for(int i=head[now];i;i=e[i].nxt)if(e[i].t!=fa)dfs(e[i].t,now);
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
while(dep[x]>dep[y])x=f[x][loog[dep[x]-dep[y]]-1];
if(x==y)return x;
for(int k=loog[dep[x]]-1;k>=0;k--)if(f[x][k]!=f[y][k])x=f[x][k],y=f[y][k];
return f[x][0];
}
void init (int n){for(int i=1;i<=n;i++)loog[i]=loog[i-1]+(1<<loog[i-1]==i);return;}
int main(){
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
init(m);
for(int i=1;i<=n-1;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
dfs(s,0);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
//求赞