P3379 【模板】最近公共祖先(LCA)

本文介绍了如何解决给定有根多叉树中两个点的最近公共祖先问题。通过建立father数组并利用二进制求解技巧,可以有效地进行查询。文章提供了样例输入输出,以及使用链式向前星存图的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门 洛谷P3379

题目描述

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

输入

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

输出

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

样例

  • Input
    5 5 4
    3 1
    2 4
    5 1
    1 4
    2 4
    3 2
    3 5
    1 2
    4 5
  • Output
    4
    4
    1
    4
    4

题解

  • 用father[i][j]表示节点i向上走2^j的节点,father[i][0]自然就是i的父节点,father[i][j]=father[father[i][j-1]][j-1]
  • 当查询a和b的最近公共祖先时,最暴力的方法就是先把两个节点深度统一,然后一起往上一步一步走,到相同为止。用father数组处理后,每次就可以一段一段地往上面走。
  • 假设a的深度比b大,先将a移动到和b深度相同的位置,每次向上走2^(log(depth[x]-depth[y])/log(2.0))个,直到a,b深度相同为止。
  • 当两个深度相同后,从i=log(depth[x])/log(2.0)开始,只要father[x][i]和father[y][i]不同,就往上面跳,i递减,这样只要他们有公共祖先,就一定能跳到公共祖先的孩子位置。
  • 用递推的方法求lg数组,lg[i]表示log(i)/log(2)+1.
  • 链式向前星存图

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<bits/stdc++.h>
#define INIT(a,b) memset(a,b,sizeof(a))
#define LL long long
using namespace std;
const int maxn=5e5+7;
const int maxm=5e5+7;
const int inf=1<<32-1;
int Begin[maxn],father[maxn][22],depth[maxn],lg[maxn];
struct Edge{
int to,next;
}ae[maxm<<1];
int tot=0,n,m,s;
void add(int x,int y){
ae[tot]=(Edge){y,Begin[x]};
Begin[x]=tot++;
}
void dfs(int now,int fa){
depth[now]=depth[fa]+1;
father[now][0]=fa;
for(int i=1;(1<<i)<=depth[now];i++)
father[now][i]=father[father[now][i-1]][i-1];
for(int i=Begin[now];~i;i=ae[i].next){
if(ae[i].to!=fa)dfs(ae[i].to,now);
}
}
int Lca(int x,int y){
if(depth[x]<depth[y]) swap(x,y);
while(depth[x]>depth[y])
x=father[x][lg[depth[x]-depth[y]]-1];
if(x==y)return x;
for(int i=lg[depth[x]];i>=0;i--)
if(father[x][i]!=father[y][i])
x=father[x][i],y=father[y][i];
return father[x][0];
}
int main(){
INIT(Begin,-1);
scanf("%d %d %d",&n,&m,&s);
int x,y;
for(int i=0;i<n-1;i++){
scanf("%d %d",&x,&y);
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
dfs(s,0);
while(m--){
scanf("%d %d",&x,&y);
printf("%d\n",Lca(x,y));
}
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值