最近公共祖先(LCA)(树上倍增)

什么是最近公共祖先呢?其实这是在树上定义的,比如说x节点,那么x节点以及x节点的父节点以及x节点的父节点的父节点的……父节点,这都算是x的祖先,而公共祖先显然是对于两个节点而言的,就是某个节点同时是两个节点的祖先,那么这个节点就是两个节点的公共祖先,最近公共祖先当然是离他们最近的公共祖先了。画个图来说明一下:

显然C和D都是A和B的公共祖先,由于C离A和B距离更近,所以C就是A和B的最近公共祖先,下面我主要来说一下A和B的公共祖先应该怎么求,首先我们需要求出所有点的深度,这个用一个搜索就可以直接得到,这个不必多说,除此之外,我们还需要预处理出来一个数组f,f[i][j]表示以i为节点向上找2^j个父亲节点所得到的父亲节点,比如f[A][1]就是A节点上面找2^1个父亲节点所得到的节点C,同理f[B][2]就是D,那么这个数组怎么求出来呢?其实也不难,我们想首先我们在搜索函数中会传两个参数,一个是当前节点x,另一个就是当前节点的父亲节点father,所以当前节点的直接父节点就有了,也就是f[x][0]=father,然后我们就可以用f[x][i]=f[f[x][i-1]][i-1]来更新了,因为我们函数是从根节点向下搜索的,所以我们一定在用求f[x][i]节点时,f[f[x][i-1]][i-1]肯定已经更新完成了,所以我们就可以用更新完的值去更新当前f数组,更新思想类似于ST表更新思想。

具体代码:

d[x]=d[father]+1;//计算深度 
f[x][0]=father;
for(int i=1;i<=20;i++)
	f[x][i]=f[f[x][i-1]][i-1];

好,现在我们得到了f数组以及深度d数组,下面该说怎么用这两个数组来求最近公共祖先了。

首先我们需要将A和B中深度较大的点移至与B同一高度,图中显然是B深度更大,我们不妨假设A与B之间的深度差为k,其二进制表示为1101,那么我们可以先将B向上移动2^3=8格,然后A与B之间的深度差就变为0101,然后再将B向上移动2^2=4格,然后A与B之间的深度差就变为0001,最后再将B向上移动2^0=1格,然后A与B就在同一高度了,我们就是按照这样的思想将A和B移至同一高度的,那么如何将B移动2^i格呢,其实这个也就用到了我们的f数组,我们令x=f[x][i]不就实现了将B向上移动2^i格了吗?至此我们也已经实现了将B与A移至同一层次了。

相应代码:

for(int i=20;i>=0;i--)//需要先将x和y移至同一高度 
	if(d[f[x][i]]>=d[y]) 
		x=f[x][i];

下面就相当于已知A和B在同一层次然后求A和B最近公共祖先了,我们先来看一下A和B的公共祖先满足什么性质吧,其中最明显的一个性质就是如果T是A和B的公共祖先,那么T的祖先节点就是A和B的公共祖先,所以我们可以从下往上找,直到找到一个节点其父节点就是A和B的最近公共祖先。这里的方法和上面将A和B移至同一高度的方法类似,但又有一些不同,就是我们依旧是从高位开始for循环,当满足向上移动2^j格后得到的节点不是两者公共祖先时就移动,如果是就不移动,其实原因很简单,因为如果是公共祖先就移动的话,那么for循环的第一次可能就直接移动了,虽然是其公共祖先,但却不是最近的,所以我们的目的是先找出来A和B最近公共祖先下面的一个节点,然后直接通过f[x][0]找到其最近公共祖先即可。

相应代码:

for(int i=20;i>=0;i--)
    if(f[x][i]!=f[y][i])
	{
		x=f[x][i];
		y=f[y][i];
	}

最后需要注意的一点就是我们最后找到的是A和B最近公共祖先的下一个节点,这也就意味着我们需要输出f[x][0],但是如果将A和B移至同一层次后发现A和B是同一个节点,也就是说A和B的公共节点是A和B其中一个节点,这时候就要直接输出了,所以要记得特判这种情况。

树上倍增题目常常可能会加入一些数组dp[i][j]表示从i节点到i节点上面的第2^j个父节点这段区间的某种性质(比如说最大值,最小值之类的)

下面给出一道例题并给出其代码:

【模板】最近公共祖先(LCA) - 洛谷

题目描述

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

输入格式

第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N-1行每行包含两个正整数x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。

输出格式

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

输入输出样例

输入

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

输出

4
4
1
4
4

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e6+10;
int f[N][21],e[N],ne[N],h[N],idx,d[N];
void add(int x,int y)
{
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs(int x,int father)
{
	d[x]=d[father]+1;//计算深度 
	f[x][0]=father;
	for(int i=1;i<=20;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;
		dfs(j,x);
	}
}
int main()
{
	int n,m,s;
	cin>>n>>m>>s;
	int u,v;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	dfs(s,0);
	int x,y;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		if(d[x]<d[y]) swap(x,y);
		for(int i=20;i>=0;i--)//需要先将x和y移至同一高度 
			if(d[f[x][i]]>=d[y]) 
				x=f[x][i];
		if(x==y)
		{
			printf("%d\n",x);
			continue;
		}
		for(int i=20;i>=0;i--)
			if(f[x][i]!=f[y][i])
			{
				x=f[x][i];
				y=f[y][i];
			}
		printf("%d\n",f[x][0]);
	}
	return 0;
}

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值