最近公共祖先(LCA)(链式前向星+倍增法)

题目描述

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

输入格式

第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N-1N1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的最近公共祖先。

输出格式

输出包含 MM 行,每行包含一个正整数,依次为每一个询问的结果。

输入输出样例

输入 #1
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出 #1
4
4
1
4
4

说明/提示

对于 30\%30% 的数据,N\leq 10N10

对于 70\%70% 的数据,N\leq 10000N10000

对于 100\%100% 的数据,N\leq 500000N500000,

样例说明:

该树结构如下:

第一次询问:2, 42,4 的最近公共祖先,故为 44。

第二次询问:3, 23,2 的最近公共祖先,故为 44。

第三次询问:3, 53,5 的最近公共祖先,故为 11。

第四次询问:1, 21,2 的最近公共祖先,故为 44。

第五次询问:4, 54,5 的最近公共祖先,故为 44。

故输出依次为 4, 4, 1, 4, 44,4,1,4,4。

#include <bits/stdc++.h>
using namespace std;
//利用倍增法,找到最近公共祖先

//链式前向星的存储方式 
/*
缺点:
1.重边不好处理
2.不能通过两点马上确定对应的边 
*/
int n,m,s;

const int maxn = 500000+5;
int k;//边号 
struct edge{
    int v;//边对应的另一个顶点 
    int next;//下一条边好,如果没有的话,为-1 
}E[maxn*2];//无向图,所以边要存两遍
int head[maxn];//head[i],记录顶点i的第一条边 

int d[maxn],p[maxn][21];
//d[i]存的是i节点对应的深度,p[i][j]对从i位置向上走2的j次方所能到达的位置


//新增的边成为顶点u所对应的第一条边 
void add(int u,int v){
    E[k].v = v;
    E[k].next = head[u];//头插法,将该边插在最前面
    head[u] = k++; 
} 


void dfs(int u,int f){
    d[u] = d[f]+1;
    p[u][0] = f;
    //边界确保最最多跳两次即达到根节点边界处 
    for(int i=1;(1<<i)<=d[u];i++){
        p[u][i] = p[p[u][i-1]][i-1];
        //相当于u的2的i次方祖先的位置 == u的2的i-1祖先的位置再往上2的i-1
        //2^i = 2^(i-1)+2^(i-1)=2*2^(i-1) 
    } 
    //从上到下递归
    for(int i=head[u];i!=-1;i=E[i].next){
        int v = E[i].v;
        if(v!=f){
            dfs(v,u);
            //保证递归的时候不陷入相应的死循环 
        } 
    } 
}

int lca(int a,int b){
    if(d[a]>d[b]){
        swap(a,b);//保证a的深度小 
    }
    //先将a,b处理成同一高度
    for(int i=20;i>=0;i--){
        //b先往上跳 
        //先从跨度大的开始跳,然后确保二者高度最终达到一致的标准 
        if(d[a]<=d[b]-(1<<i)){
            b = p[b][i];
        }
    }
    //如果移动到同一层次后,重合,则返回相应的答案 
    if(a==b) return a;
    for(int i=20;i>=0;i--){
        if(p[a][i]==p[b][i]) continue;//跳度大的地方均相同 
        //跳到小一级的地方
        a = p[a][i],b = p[b][i]; 
    } 
    return p[a][0];
    
} 
int main(){
    memset(head,-1,sizeof(head));
    int a,b; 
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<n;i++){
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    dfs(s,0);//从根节点出发,开始进行一遍预处理 
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));
    }
    return 0;
}

 

posted @ 2021-05-17 18:52  zmachine  阅读(165)  评论(0编辑  收藏  举报