倍增求LCA问题学习笔记

什么是LCA?

这是最近公共祖先问题,就是求出树上两个结点的最近公共祖先。

思考:

显然,可以我们一级一级的去向上搜索,直到遇见它们的最近公共祖先。不过在数据大的情况下,这样显然会T飞了,所以我们要考虑优化。

优化后其实一共有好多种方法,这里只介绍倍增法。

倍增法原理:

倍增法是改变暴力一次只向上找一层的策略,去进行一次向上找 2^i 级的策略,即跳到它的第 2^i 个祖先,这样就可以把 O(n) 的复杂度降到 O(log) 的级别,大幅优化时间复杂度。

如何实现一次跳到它的第 2^i 个祖先?

预处理即可,在dfs整个树的时候预处理出 lca 数组,这样跳的时候就可以直接跳了,lca[x][i] 即指 x 的第 2^i 个祖先。至于如何处理,见代码,自行理解一下就可以了。

	lca[x][0]=fa;
	for(int i=1;i<=20;i++)	lca[x][i]=lca[lca[x][i-1]][i-1];

那究竟如何具体的去跳祖先?

首先我们显然让两个查询的点到达同一深度,否则无法进行后续同步往上跳祖先的活动。代码如下:。

	if(dep[x]<dep[y])	swap(x,y);
	for(int i=20;i>=0;i--)
	{
		if(dep[lca[x][i]]>=dep[y])	x=lca[x][i];
	}

在到达同一深度后,我们再进行同步的往上跳即可,代码如下:

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

需要注意的是这两次我们都需要从大到小枚举 i 这样才能正确的跳祖先(因为正着枚举显然不成立,这个手模一下即可)。

需要注意的是最后的答案应该是:

        lca[x][0]

完整代码如下:

#include<bits/stdc++.h>
using namespace std;

vector<int>a[500005];
int n,m,s,dep[500005],lca[500005][21];
void dfs(int x,int fa)
{
	dep[x]=dep[fa]+1;
	lca[x][0]=fa;
	for(int i=1;i<=20;i++)	lca[x][i]=lca[lca[x][i-1]][i-1];
	int l=a[x].size();
	for(int i=0;i<l;i++)
	{
		int v=a[x][i];
		if(v==fa)	continue;
		dfs(v,x);
	}
}
int LCA(int x,int y)
{
	if(dep[x]<dep[y])	swap(x,y);
	for(int i=20;i>=0;i--)
	{
		if(dep[lca[x][i]]>=dep[y])	x=lca[x][i];
	}
	if(x==y)	return x;
	for(int i=20;i>=0;i--)
	{
		if(lca[x][i]!=lca[y][i])
		{
			x=lca[x][i];
			y=lca[y][i];
		}
	}
	return lca[x][0];
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m>>s;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(s,0);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		cout<<LCA(x,y)<<endl;
	}
	return 0;
}

如有错误求大佬指出,蒟蒻感激不尽qaq。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值