洛谷3379 【模板】最近公共祖先(LCA) 树上倍增+LCA

题目

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

题解

树上倍增和普通的倍增原理是一样的,它的运用很广泛,除了求LCA外,在很多问题中都有应用
倍增就是将状态空间中2的整数次幂的值作为代表,当要查询其它位置的值时,可以通过“任意整数可以表示成若干个2的次幂项的和”这一性质,使用之前求出的代表值拼成所需的值。

在树上倍增求LCA中,设f[i][k]表示点i的2^k辈父亲,而f[i][0]表示i的父节点。通过一个广搜或深搜预处理出所有节点的f,还有节点的深度。其中f[i][k]=f[f[i][k-1]][k-1],1<=k<=log(n)/log(2)+1
求LCA时,先把两个节点弄到同一高度,然后一起往上跳,最终跳到的一定是它们最近公共祖先的子节点,答案就得出了(详见注释)

代码

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <queue>

using namespace std;

const int N=500005;
int n,m,s,t,cnt;
int ls[N],ne[N*2],to[N*2],b[N];
int f[N][22];
queue<int> q;

void bfs(){
    q.push(s);b[s]=1;f[s][0]=s;
    while (q.size()){
        int k=q.front(),e;
        q.pop();
        e=k;
        for (k=ls[k];k;k=ne[k]){
            if (b[to[k]]) continue;
            b[to[k]]=b[e]+1;
            f[to[k]][0]=e;
            for (int i=1;i<=t;i++)
                f[to[k]][i]=f[f[to[k]][i-1]][i-1];
            q.push(to[k]);
        }
    }
}

int lca(int c,int d){
    if (b[c]>b[d]) swap(c,d);//比较的是深度,不是点的标号!!!!
    for (int i=t;i>=0;i--)
        if (b[f[d][i]]>=b[c]) d=f[d][i];//把深度大的跳到和深度小的同一深度
    //只在d的父亲的深度大于等于深度小的c时跳,这样就不会跳过头,通过2的次幂组合可以刚好跳到
    if (c==d) return c;//当c刚好就是LCA时
    for (int i=t;i>=0;i--)
        if (f[c][i]!=f[d][i]) c=f[c][i],d=f[d][i];
    //一起跳,只在两点父辈不同时跳,可以通过2的次幂项组合跳到LCA的儿子节点
    return f[c][0];
}

int main(){
    scanf("%d%d%d",&n,&m,&s);
    t=(int)(log(n)/log(2))+1;
    for (int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        ne[++cnt]=ls[a];ls[a]=cnt;to[cnt]=b;
        ne[++cnt]=ls[b];ls[b]=cnt;to[cnt]=a;
    }
    bfs();
    for (int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));
    }
}

猜你喜欢

转载自blog.csdn.net/yjy_aii/article/details/81699554