倍增求lca

本文介绍了最近公共祖先(LCA)的求解方法。首先指出简单“暴力”法时间复杂度高不适用,接着重点讲解利用倍增求LCA,包括预处理求f[i][j]数组,以及求LCA的具体流程,最后给出完整代码思路。

模板题详见洛谷P3379

什么是lca

l c a lca lca,即 最近公共祖先。具体解释请自行百度~~~(我太弱了)

lca怎么求

1.最简单的方法则是“暴力”。将深度大的节点向上走到与深度小的节点的深度相同;之后两个节点一起向上跳,知道它们所在的节点相同。
但是这种做法的时间复杂度太高了 Q A Q QAQ QAQ ,所以并不适合。

2.所以考虑利用倍增求lca。先开一个 f [ ] [ ] f[ ] [ ] f[][] 数组, f [ i ] [ j ] f[ i ][ j ] f[i][j] 表示节点i向上跳 2 j 2^j 2j个单位后的节点,之后再“暴力”求解,这样复杂度就少很多了!

流程

1.常规建边(这个不用解释吧)

void add(int x,int y)
{
	tot++;
	tr[tot].v=y;
	tr[tot].next=head[x];
	head[x]=tot;
}//建边

2.预处理
d e p [ i ] dep[ i ] dep[i]表示 节点i的深度, f a [ i ] fa[ i ] fa[i],表示节点i的父节点(其实并没有什么用),重点就是求 f [ i ] [ j ] f[ i ][ j ] f[i][j]。可以证明,节点i向上跳 2 j 2^j 2j个节点(即 f [ i ] [ j ] f[i][j] f[i][j])就是节点i向上跳 2j-1个节点后的节点(即 f [ i ] [ j − 1 ] f[i][j-1] f[i][j1])再向上跳2j-1个节点,因为2j-1+2j-1=2j。(原谅我语文不好 Q A Q QAQ QAQ,其实可以自己推一下)

void ready(int u,int v)//v是u的父亲
{
	dep[u]=dep[v]+1;
	fa[u]=v;
	//f[u][0]=v;
	for(int i=0;i<=20;i++)
	f[u][i+1]=f[f[u][i]][i];//预处理倍增数组
	for(int t=head[u];t;t=tr[t].next)
	{
		int nw=tr[t].v; 
		if(nw==v)continue;
		f[nw][0]=u;
		ready(nw,u);//dfs
	}
}//预处理

3.接下来就是重点——求 l c a lca lca
假设要求 x x x y y y节点的 l c a lca lca,且 x x x y y y深,那么我们就利用倍增,先让 x x x向上跳,直到与 y y y的深度相同。若此时 x x x等于 y y y节点,则直接返回 x x x y y y节点。若不相同,算法继续。

接下来让 x x x y y y一起向上跳,方法还是倍增。但需注意的是不能让 x x x y y y跳到 l c a lca lca以上的节点,也就是 f [ x ] [ i ] ! = f [ y ] [ i ] f[x][i]!=f[y][i] f[x][i]!=f[y][i]。最后 x x x y y y会分别是 l c a lca lca的子节点,直接返回 x x x y y y的父节点(即 f [ x ] [ 0 ] f[x][0] f[x][0] f [ x ] [ 0 ] f[x][0] f[x][0])即可。

int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(int i=20;i>=0;i--)
	{
		if(dep[f[x][i]]>=dep[y])x=f[x][i];//保证x一直比y深或与y深度相同
		if(x==y)return x;//有答案则直接得出答案
	}//让x与y的深度相同
	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];//最终是在lca的子节点停下来
}//先跳到同一深度,再同时向上跳

完整代码

#include<cstdio>
#include<algorithm>
#define maxn 500005
using namespace std;

struct tree
{
	int v,next;
}tr[maxn*4];

int n,m,s,tot,head[maxn];
int f[maxn][25],fa[maxn],dep[maxn],ans[maxn];//f[i][j]表示点i跳2^j的节点,dep表示节点的深度

void add(int x,int y)
{
	tot++;
	tr[tot].v=y;
	tr[tot].next=head[x];
	head[x]=tot;
}//建边

void ready(int u,int v)//v是u的父亲
{
	dep[u]=dep[v]+1;
	fa[u]=v;
	//f[u][0]=v;
	for(int i=0;i<=20;i++)
	f[u][i+1]=f[f[u][i]][i];//预处理倍增数组
	for(int t=head[u];t;t=tr[t].next)
	{
		int nw=tr[t].v; 
		if(nw==v)continue;
		f[nw][0]=u;
		ready(nw,u);//dfs
	}
}//预处理

int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(int i=20;i>=0;i--)
	{
		if(dep[f[x][i]]>=dep[y])x=f[x][i];//保证x一直比y深或与y深度相同
		if(x==y)return x;//有答案则直接得出答案
	}//让x与y的深度相同
	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];//最终是在lca的子节点停下来
}//先跳到同一深度,再同时向上跳

int main()
{
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}//建边
	ready(s,0);//预处理
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		ans[i]=lca(x,y);
	}
	for(int i=1;i<=m;i++)
	printf("%d\n",ans[i]);
	return 0;
}

终于写完了,我还是太弱了。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值