[模板]最近公共祖先LCA

本文介绍了一种利用倍增算法求解树上最近公共祖先(LCA)的问题。通过深度优先搜索(DFS)计算节点深度,并预处理跳跃关系,实现了在O(logN)时间内求解LCA。在代码实现中,特别注意了处理节点深度和跳跃过程中的细节,确保了算法的正确性和效率。
摘要由CSDN通过智能技术生成

目录

Description

Format

Input

Output

Samples

输入数据 1

输出数据 1

分析

代码


Description

给出N,Q .N<=5*10^5 代表一个树有N个点 ,树的根为1 Q代表有Q个询问,询问A,B的最近公共祖先是哪一个.

Format

Input

第一行给出N,Q 下面N-1行描述这个树,格式为A,B,代表A为B的Son ,接来下Q行 给出A,B,代表进行询问A,B的LCA是哪一个.

Output

针对每个询问输出结果

Samples

输入数据 1

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

输出数据 1

2
2

提示 请使用printf语句进行输出.用cout会TLE

分析

首先,这些多组数据题一般是用来卡时间的

既然知道了,那么就要想一想该怎么办。

这里我们会用到一个叫做“倍增”的算法。

大家可以看看这篇文章:

https://blog.csdn.net/chengzhang41103/article/details/123600247https://blog.csdn.net/chengzhang41103/article/details/123600247在这篇文章中,我们在线性结构上,从一次跳一步变成了一次跳2^{n}步。

那么为什么不在树上也这样做呢?毕竟树也是由线性结构组成的。

接下来进入正题


首先,每个点跳2^{0}步就是它的父亲点

而跳2^{n}步就等于先跳2^{n-1}步再跳2^{n-1}

那么递归公式就是:f[i][j]=f[f[i][j-1]][j-1] 。

预处理完以后,就可以开始lca了。

先dfs一遍,把每个点深度求出来。

注意:根节点的深度为1,因为根节点的上方在dfs时又会出现一个0号节点,而深度不应为负数。

然后,得到两个点:假如他们是x,y

首先,确保x深度更深。用i逆循环从20到1,用f数组向上跳,

只要x跳2^{i}步后深度依然大于y

就使x=f[x][i]。

第二步。倘若跳完后,x==y,那么lca就是x或y了

否则,两个点一起往上跳,只要不跳到同一个点上就行(why?先想一想,答案在代码中)。

经过第二步后,x就跳到了lca的下面,输出f[x][0]就可以了

另外,在代码实现时,还有一些小细节。题目中给出的条件一定要用上。

看到题目中那行加黄的字了吗?那可是原题中的。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1000100;
int front[N],last[N],kid[N];
int flor[N],f[N][21];
int n,x,y,num,q,o;
void put(int x,int y)
{
	num++;
	front[num]=last[x];
	last[x]=num;
	kid[num]=y;
}
void did(int father,int child,int dep)
{
	flor[child]=dep;
	for(int i=last[child];i;i=front[i])
		if(father!=kid[i])
			did(child,kid[i],dep+1);
}
int lca(int x,int y)
{
	if(flor[x]<=flor[y])
		swap(x,y);
	for(int i=20;i>=0;i--)
		if(flor[f[x][i]]>=flor[y])
			x=f[x][i];
	if(x==y)
		return x;
	for(int i=20;i>=0;i--)
		if(f[x][i]!=f[y][i])
        //如果跳到同一个点上,则一定是公共祖先,但不一定是lca
		{
			x=f[x][i];
			y=f[y][i];
		}
	return f[x][0];
}
int main()
{
	cin>>n>>q;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
		put(y,x);
		f[x][0]=y;
	}	
	did(0,1,1);
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++)
			f[i][j]=f[f[i][j-1]][j-1];
	for(int i=1;i<=q;i++)
	{
		cin>>x>>y;
		o=lca(x,y);
		printf("%d\n",o);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值