【洛谷 3379】最近公共祖先【LCA】【倍增】

9 篇文章 0 订阅
8 篇文章 0 订阅

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


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

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


输入输出样例
输入 #1
1 2 3

输出 #1
2

输入 #2
1 5 3

输出 #2
2


知识点:LCA问题:

定义:基于有根树最近公共祖先问题
在有根树T中,询问一个距离根最远的结点x,使得x同时为结点 u 、 v u、v uv的祖先,这个祖先节点即为 l c a lca lca。同时 l c a lca lca一定是 u 、 v u、v uv路径上的点。


解题思路
我们可以有这样一个思路,先让 u 、 v u、v uv中深度大的那个点先往上走,直到两点深度相同。然后一起往上走,知道两点相遇,这个点就是 l c a lca lca

接着我们想,如果我们每次只走一步,显然单次查询lca复杂度为 O ( n ) O(n) O(n)。那有没有在空间合适的条件下更好的算法呢?——我们可以用倍增 倍增 的思想

在这里插入图片描述
想要实现这个算法,首先我们要记录各个点的深度和他们 2 i 2^i 2i级的的祖先,用数组 d e p dep dep表示每个节点的深度, f [ i ] [ j ] f[i][j] f[i][j]表示节点 i i i 2 j 2^j 2j级祖先。 这样如果我们需要走k步,那只需要跳 O ( l o g n ) O(logn) O(logn)次即可。

转移

  • f [ i ] [ j ] = f a [ i ] ( j = 0 ) f[i][j]=fa[i] (j=0) f[i][j]=fa[i](j=0)
    f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] ( j > 0 ) f[i][j]=f[f[i][j-1]][j-1] (j>0) f[i][j]=f[f[i][j1]][j1](j>0)

这个转移可以说是算法的核心之一,意思是 i i i 2 i 2^i 2i祖先等于 i i i 2 ( i − 1 ) 2^(i-1) 2(i1)祖先的 2 ( i − 1 ) 2^(i-1) 2(i1)祖先 ,也就是走 2 j 2^j 2j步相当于先走 2 ( j − 1 ) 2^(j-1) 2(j1)步再走 2 ( j − 1 ) 2^(j-1) 2(j1)步。

代码如下:

void dfs(int x,int fa){//x表示当前节点,fa表示它的父亲节点
	f[x][0]=fa,dep[x]=dep[fa]+1;
	for(int i=1;i<=lg[dep[x]];i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int i=h[x];i;i=a[i].next)
		if(a[i].x!=fa)
			dfs(a[i].x,x);
}

预处理完毕后,我们就可以去找它的 L C A LCA LCA了,为了让它跑得快一些,我们可以加一个常数优化。。。(来自洛谷提高组讲义)

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

接下来就是倍增 L C A LCA LCA了,按前面说到的我们先把两个点提到同一高度,再统一开始跳。

int LCA(int x,int y){
	if(dep[x]>dep[y]) swap(x,y);//用数学语言来说就是:不妨设x的深度 <= y的深度
	while(dep[y]>dep[x])
		y=f[y][lg[dep[y]-dep[x]]-1];//先跳到同一深度
	if(x==y)//如果x是y的祖先,那他们的LCA肯定就是x了
		return x;
	for(int i=lg[dep[x]]-1;i>=0;i--)//不断向上跳(lg就是常数优化)
	{
		if(f[x][i]!=f[y][i])//因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
		{
			x=f[x][i];
			y=f[y][i];
		}
	} 
	return f[x][0];//返回父节点
}

AC代码

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,s,x,y,k,h[500010],lg[500010],dep[500010],f[500010][22];
struct c{
	int x,next;
}a[1000010];
void add(int x,int y){
	a[++k].x=y;
	a[k].next=h[x];
	h[x]=k;
}
void dfs(int x,int fa){
	f[x][0]=fa,dep[x]=dep[fa]+1;
	for(int i=1;i<=lg[dep[x]];i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int i=h[x];i;i=a[i].next)
		if(a[i].x!=fa)
			dfs(a[i].x,x);
}
int LCA(int x,int y){
	if(dep[x]>dep[y]) swap(x,y);
	while(dep[y]>dep[x])
		y=f[y][lg[dep[y]-dep[x]]-1];
	if(x==y)
		return x;
	for(int i=lg[dep[x]]-1;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	} 
	return f[x][0];
}
int main(){
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;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);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",LCA(x,y));
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值