洛谷 P3379:最近公共祖先(LCA)← 倍增+链式前向星

70 篇文章 3 订阅
4 篇文章 0 订阅

【题目来源】
https://www.luogu.com.cn/problem/P3379

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

【输入格式】
第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N−1 行每行包含两个正整数 x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。

【输出格式】
输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。

【输入样例】
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

【输出样例】
4
4
1
4
4

【说明/提示】
对于 30% 的数据,N≤10,M≤10。
对于 70% 的数据,N≤10000,M≤10000。
对于 100% 的数据,1≤N,M≤500000,1≤x,y,a,b≤N,不保证 a≠b。

【算法分析】
● 一般来讲,求 LCA 有 3 种常见方法:
倍增RMQ+欧拉序Tarjan(离线)。本题代码介绍“倍增”法求LCA。

● 设
f[x][k] 表示 x 的 2^k 辈祖先,则有 f[x][k]=f[f[x][k-1]][k-1]。显然,f[x][0] 就是 x 的 2^0=1 辈祖先,即 x 的父结点。其中,k∈[1, logn],n为树的结点个数。

● 链式前向星:https://blog.csdn.net/hnjzsyjyj/article/details/139369904
val[idx]:存储编号为 idx 的边的值
e[idx]:存储编号为 idx 的结点的值
ne[idx]:存储编号为 idx 的结点指向的结点的编号
h[a]:存储头结点 a 指向的结点的编号

【算法代码】

#include <bits/stdc++.h>
using namespace std;

const int N=5e5+5;
const int M=N<<1;
int h[N],e[M],ne[M],idx;
int dep[N];
int f[N][20]; //f[x][k] represents the 2^k ancestor of x
int root;
int n,m;

void add(int a,int b) {
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int fa) {
    f[u][0]=fa;
    dep[u]=dep[fa]+1;
    for(int i=h[u]; ~i; i=ne[i]) {
        int j=e[i];
        if(j==fa) continue;
        dfs(j,u);
    }
    return;
}

int LCA(int x,int y) {
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=20; i>=0; i--) { //log500000 < 20
        if(dep[f[x][i]]>=dep[y]) x=f[x][i]; //dep[x]-(1<<i) equal to dep[f[x][i]]
    }
    if(x==y) return x;
    for(int i=20; i>=0; i--) {
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    }
    return f[x][0];
}

int main() {
    memset(h,-1,sizeof(h)); //emphasis
    cin>>n>>m>>root;
    for(int i=1; i<n; i++) {
        int u,v;
        cin>>u>>v;
        add(u,v);
        add(v,u);
    }

    dfs(root,-1);

    for(int j=1; (1<<j)<=n; j++) {
        for(int i=1; i<=n; i++) {
            f[i][j]=f[f[i][j-1]][j-1];
        }
    }

    while(m--) {
        int x,y;
        cin>>x>>y;
        cout<<LCA(x,y)<<endl;
    }

    return 0;
}

/*
in:
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

out:
4
4
1
4
4
*/




【参考文献】
https://blog.csdn.net/Lean_Feather/article/details/113057714
https://blog.csdn.net/qq_49705495/article/details/131969401
https://blog.csdn.net/hnjzsyjyj/article/details/139369904



 

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值