最近公共祖先(LCA):倍增

https://www.luogu.org/problem/show?pid=3379
最近公共祖先方法有很多的,现在我们利用倍增表求lca
何为倍增表,简单的说是成倍增加表
bz[i][j]表示在第i位上向前推进2^j步
对于一颗有根树,bz[i][j]表示从第i为向根节点走2^j步,就是说高度增加2^j
倍增表功能强大,这里不展开(我不会…)
首先我们讲一下倍增
它的地推公示bz[i][j]=bz[bz[i][j-1]][j-1]
和st表很像的
对于一棵树,我们可以dfs求出bz[i][0]
就是i节点的父节点2^0=1;
然后

for(int j=1;n>=(1<<j);j++)
        for(int i=1;i<=n;i++)
            if(bz[i][j-1])
                bz[i][j]=bz[bz[i][j-1]][j-1];

在爆搜的同时,我们直接求出deep[i],即深度
然后每读入两个数x,y(deep[y]>=deep[x]),如果他们有高度差,先把较深的点顺着根节点向上爬,爬到两个点相同高度;
首先我们搞一个j=0;不断增加j,使deep[bz[y][j]]>deep[x]
那么deep[bz[y][j-1]]一定小于deep[x]且距离deep[x]较近
显然因为y和bz[y][j-1]都在x的下面所以他们公共最近祖先是一样的
那我们把y提升到bz[y][j-1]的位置
继续重复,直到deep[x]==deep[y];
当然咯j不断增加,有可能bz[y][j]比根节点还大,但这时deep[bz[y][j]]是0,所以不会对答案有影响;

提升到同一高度后,我们就可以同时提升xy,方法和上面一样
直到x==y当然,每个节点本身也是自己的祖先

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
struct cs{
    int to,next;
}a[2000004];
int head[600005],deep[600005],bz[600001][25];
int p,n,m,x,y,z,ll;
void inc(int x,int y){
    ll++;
    a[ll].to=y;
    a[ll].next=head[x];
    head[x]=ll;
}
void dfs(int x,int y,int z){
    bz[x][0]=y;
    deep[x]=z;
    int k=head[x];
    while(k){
        if(a[k].to!=y) dfs(a[k].to,x,z+1);
        k=a[k].next;
    }
}
int upone(int stdd,int x){//把两个点提升到相同高度 
    while(deep[x]!=stdd){
        int j=0;
        while(deep[bz[x][j]]>=stdd)j++;
        x=bz[x][j-1];
    }
    return x;
}
int happytogether(int x,int y){//两个点一起提升,只现在更新x点,从y点过来 
    if(x==y)return x;
    while(1){
        int j=0;
        if(bz[x][j]==bz[y][j])return bz[x][j];
        while(bz[x][j]!=bz[y][j])j++;
        j--; x=bz[x][j]; y=bz[y][j];
    }
}
int lca(int x,int y){
    if(deep[x]>deep[y])swap(x,y);
    y=upone(deep[x],y);
    return(happytogether(x,y));
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1;i<=n-1;i++){
        scanf("%d%d",&x,&y);
        inc(x,y);
        inc(y,x);
    }
    dfs(p,-6,1);
    for(int j=1;n>=(1<<j);j++)
        for(int i=1;i<=n;i++)
            if(bz[i][j-1])
                bz[i][j]=bz[bz[i][j-1]][j-1];
    while(m--){
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
}
posted @ 2017-01-22 18:33  largecube233  阅读(140)  评论(0编辑  收藏  举报