如何在 DAG 中找多个点的 LCA ?

点集S的LCA定义为, 一个公共祖先n是LCA当且仅当不存在一个S的公共祖先t, 使得n是t的祖先。 x是y的祖先的定义是从x可达y。 当然可能存在多…
关注者
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;
}

//求赞