NKOI 2447 最近公共祖先

本文介绍了解决最近公共祖先(LCA)问题的一种有效方法——LCA算法,并通过具体实例详细阐述了该算法的工作原理及实现过程。文章展示了如何利用树上倍增技术进行节点间的快速查询,适用于大规模树状结构的数据处理。

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

最近公共祖先

Time Limit:10000MS  Memory Limit:65536K
Total Submit:104 Accepted:91
Case Time Limit:1000MS

Description

给出一棵有N(编号1到N)个节点的有根树,求出指定节点对的最近公共祖先!

对于树中节点x而言,从根节点到达x的这一条路径中经过的所有节点,都称为x的祖先。
如上图所表示的树中, 根节点为8。8、4、10、16都是12的祖先。对于6和12这对节点而言,从6出发往上朝根走和从12出发往上朝根走的两条路径最早交汇的地点是4号节点,因此4号点是6和12的最近公共祖先。
同理,11和9的最近公共祖先是8; 10和3的最近公共祖先是10;2和7的最近公共祖先是4......

Input

第一行,一个整数N。表示树中节点总数
接下来N-1行,每行两个整数x和y,表示x是y的父亲。
接下来一行,一个整数M,表示询问的总数
接下来M行,每行两个整数a和b,表示询问a和b的最近公共祖先。

Output

M行,每行一个整数,表示对应询问的答案。

Sample Input

输入样例1:
16 
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
3
16 7
14 9
3 10
输入样例2:

5
2 3
3 4
3 1
1 5
2
3 5
4 5

Sample Output

输出样例1:
4
8
10
输出样例2:
3
3

Hint

2<=N<=10000
1<=M<=10000

Source

改编自POJ1330


求有关最近公共祖先的问题可以用LCA来解决,与倍增有关,是比较实用的技巧

树上倍增算法:
Dep[v]记录节点v的深度(层数)
fa[v][k]记录节点v向上第2^k个祖先的编号

fa[v][k]=fa[fa[v][k-1]][k-1];

s为倍增的上限,比如这个树的总层数为8,则s为3(23==8)

然后对于两个不同的节点A,B我们要求他们的公共祖先,首先要用到go_up函数(在本人代码中将其和lca函数合在了一起)使层数较大的一个节点转换到一个层数与另一节点相同的祖先去,下面是代码

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;

const int maxn=10005;
int n,quest,a,b,cnt,depth[maxn],fa[maxn][35],m;
int NEXT[maxn<<1],END[maxn<<1],last[maxn];
bool mark[maxn];

inline void _read(int &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}

void insert(int a,int b){  
    END[++cnt]=b;  
    NEXT[cnt]=last[a];  
    last[a]=cnt;  
}  

void dfs(int v){<span style="font-family:Times New Roman;">//深搜建树</span>
	int i,j,k;
	depth[v]=depth[fa[v][0]]+1;
	k=ceil(log(depth[v])/log(2));//k为当前讨论的两个节点的倍增值<span style="font-family:Times New Roman;">上限</span>
	for(i=1;i<=k;i++)
	    fa[v][i]=fa[fa[v][i-1]][i-1];//递推计算fa数组
	j=last[v];
	while(j){
		dfs(END[j]);
		j=NEXT[j];
	}
}

int lca(int x,int y)
{
	int i,k,s;
	s=ceil(log(n)/log(2));  
	if(depth[x]<depth[y])swap(x,y);
	k=depth[x]-depth[y];
	for(i=0;i<=s;i++)
	    if(k&(1<<i))x=fa[x][i];//go_up操作
	if(x==y)return x;
	s=ceil(log(depth[x])/log(2));
	for(i=s;i>=0;i--)
	    if(fa[x][i]!=fa[y][i]){ x=fa[x][i]; y=fa[y][i]; }
	return fa[x][0];//最终返回公共祖先节点
}

int main(){
	_read(n);
	for(int i=1;i<n;i++){
		_read(a);_read(b);
		insert(a,b);
		fa[b][0]=a;//预处理fa数组
	}
	for(int i=1;i<=n;i++)
	    if(!fa[i][0]){
	    	dfs(i);
	    	break;
	    }
	_read(m);
	for(int i=1;i<=m;i++){
		_read(a);_read(b);
		printf("%d\n",lca(a,b));
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值