UOJ-最近公共祖先(树链剖分)

本文介绍了如何利用树链剖分解决最近公共祖先问题。在有根树中,找到两个节点的最近公共祖先,即在所有公共祖先中距离这两个节点最近的那个。文章提供了一种算法思路,并给出了输入输出描述以及样例,适用于处理大规模数据的场景。

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

UOJ-最近公共祖先

【题目描述】:

有根树在计算机科学工程领域是一个人人熟知的数据结构类型。下面是一个例子。

8->(1,4,5);1->(13,14);4->(6,10);5->(9);6->(7,15);10->(2,11,16);16->(3,12);

在这个图中,每个点都是由{1, 2,...,16}中的某个数字标记的。8号点是树的根。如果x号点在y号点到根的路径上,则x是y的祖先。比如4是16的祖先,10也是。事实上,8,4,10,16都是16的祖先。记住,一个节点本身就是自己的祖先。再比如8,4,6,7是7的祖先。

如果x既是y的祖先也是z的祖先则称x是y和z公共祖先。也就是说8和4都是16和7的公共祖先。

如果x在y和z的所有公共祖先中距离y和z最近,则x是y和z的最近公共祖先。也就是说4是16和7的最近公共祖先而不是8,因为4比8更近。

再举一些例子:节点2和3的最近共同祖先是节点10,节点6和13的最近共同祖先是节点8,节点4和12的最近共同祖先是节点4。在最后一个例子中,如果Y是Z的祖先,那么Y和Z的最近共同祖先是Y。

编写一个程序,找出树中两个不同节点的最近共同祖先。

【输入描述】:

第一行,N和M表示节点数和询问数,节点编号1至N;

以下N-1行,每行两个整数a和b,表示a是b的父亲节点;

之后M行,每行两个不相同的数,表示询问它们的最近共同祖先。

【输出描述】:

M行,每行一个数表示对应的询问结果。

【样例输入】:

16 1
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
16 7

【样例输出】:

4

【时间限制、数据范围及描述】:

时间:1s 空间:256M

对于 40%的数据:1<=N,M<=3000

对于 100%的数据:1<=N,M<=2×10^5


#include <cstdio>

#include <cstdlib>
#include <iostream>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
#define MAXN 200005
int t,f,k,n,m,a,b,l,r;
int he[MAXN],x;
int q[MAXN],dp[MAXN],fa[MAXN][20],s[MAXN];
struct Node{int v,p;}e[MAXN<<1];
void Add(int a, int b){
e[++x].v=b; e[x].p=he[a]; he[a]=x;}
void BFS(int rt){ 
int u,v; f=k=0;
memset(fa,-1,sizeof(fa));
dp[rt]=1;q[k++]=rt;
while(f<k){
u=q[f++];
for(int i=he[u];i;i=e[i].p){
v=e[i].v;
if(v==u)break;
fa[v][0]=u; dp[v]=dp[u]+1;
for(int j=1;j<20;++j)
if(fa[v][j-1]!=-1)
fa[v][j]=fa[fa[v][j-1]][j-1];
else break;
q[k++]=v;}}}
int LCA(int a, int b){
if(dp[a]<dp[b])swap(a,b);
int ta=dp[a]-dp[b];
for(int i=0;i<20;++i)
if((1<<i)&ta) a=fa[a][i];
if(a==b)return b;
for(int i=19;i>=0;--i)
if(fa[a][i]!=fa[b][i])
a=fa[a][i],b=fa[b][i];
return fa[a][0];}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;++i){
scanf("%d%d",&a,&b);
Add(a,b);
s[b]=1;}
for(int i=1;i<=n;i++)
if(!s[i]){
BFS(i);
break;}
for(int i=1;i<=m;++i){
scanf("%d%d",&l,&r);
printf("%d\n",LCA(l,r));}
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值