洛谷 P3379:最近公共祖先(LCA)← Tarjan+并查集

66 篇文章 2 订阅
6 篇文章 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(离线)。本题代码介绍“Tarjan”法求LCA。

● Tarjan算法求 LCA 是离线算法,基于后序 DFS(深度优先搜索)和
并查集

● 并查集:
https://blog.csdn.net/hnjzsyjyj/article/details/120147618

int find(int x) {
    if(x!=pre[x]) pre[x]=find(pre[x]);
    return pre[x];
}
 
void merge(int x,int y) {
    int a=find(x);
    int b=find(y);
    if(a!=b) pre[a]=b;
}

● 快读:https://blog.csdn.net/hnjzsyjyj/article/details/120131534

int read() { //fast read
    int x=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9') { //!isdigit(c)
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9') { //isdigit(c)
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}

● 链式前向星:https://blog.csdn.net/hnjzsyjyj/article/details/139369904
val[idx]:存储编号为 idx 的边的

e[idx]:存储编号为 idx 的结点的
ne[idx]:存储编号为 idx 的结点指向的结点的编号
h[a]:存储头结点 a 指向的结点的编号

● 结构体构造函数:https://blog.csdn.net/hnjzsyjyj/article/details/139553390

#include <bits/stdc++.h>
using namespace std;
 
struct Point {
    int x,y;
    Point() {}  //无参构造
    Point(int _x,int _y):x(_x),y(_y) {}  //有参构造
} pt[10];
 
int main() {
    int cnt=0;
    for(int i=1; i<=3; i++) {
        for(int j=1; j<=3; j++) {
            pt[cnt++]=Point(i,j);
        }
    }
 
    for(int i=0; i<cnt; i++) {
        printf("%d %d\n",pt[i].x,pt[i].y);
    }
 
    return 0;
}
 
/*
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
*/


【算法代码】

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

const int N=5e5+5;
int n,m,root;
int u,v;
int e[N<<1],ne[N<<1],h[N],idx=1;
int pre[N],ans[N],st[N];
struct node {
    int val,id;
    node(int _val,int _id):val(_val),id(_id) {}  //结构体构造函数
};
vector<node> q[N];

inline int read() { //fast read
    int x=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9') { //!isdigit(c)
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9') { //isdigit(c)
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}

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

inline int find(int x) {
    if(x!=pre[x]) pre[x]=find(pre[x]);
    return pre[x];
}

inline void merge(int x,int y) {
    int a=find(x);
    int b=find(y);
    if(a!=b) pre[a]=b;
}

void dfs(int u, int fa) {
    for(int i=h[u]; ~i; i=ne[i]) {
        int j=e[i];
        if(j!=fa) {
            dfs(j,u);
            pre[j]=u;
        }
        int len=q[u].size();
        for(int i=0; i<len; i++) {
            if(st[q[u][i].val]) ans[q[u][i].id]=find(q[u][i].val);
        }
        st[u]=1;
    }
}

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

    for(int i=1; i<=m; i++) {
        u=read(),v=read();
        q[u].push_back(node(v,i));
        q[v].push_back(node(u,i));
    }

    for(int i=1; i<=n; i++) pre[i]=i;

    dfs(root,0);
    for(int i=1; i<=m; i++) printf("%d\n",ans[i]);

    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/JKdd123456/article/details/81388495
https://maimai.cn/article/detail?fid=459603071&efid=m4kdxUnQtYF4sqHKVd_KtA
https://www.cnblogs.com/fu3638/p/8639429.html
https://zhuanlan.zhihu.com/p/627024371



 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值