洛谷p3379最近公共祖先(LCA)

P3379传送门
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
首先明确一下什么叫做最近公共祖先 对于一个树来说 最近公共祖先是两个结点的公共祖先中距离根结点最远的那个祖先 也可以说成是深度最大的那个公共祖先
思路
我们先用最朴素的想法来思考
如果你想找到一个最近公共祖先 那是不是先得让这两个结点爬到同一高度的时候 然后再去比较这他俩是不是同一个点呢?
①如果当他们处于同一个高度而且还是同一点的话 我们是不是就找到了他俩的最近公共祖先 就是这个点
②如果不是的话 我们是不是还得让这两个点往上爬 每爬一步就去比较这两个点是不是同一个点 直到两个点是同一个点的时候 那么此时就是最近公共祖先
这有个暴力的代码 看一下这个相对来说比较好理解点
AC代码1暴力

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+6;
int dep[maxn];
int fa[maxn];
vector<int>edge[maxn];
void dfs(int u,int v)
{
	dep[u]=dep[v]+1;
	fa[u]=v;
	for(int i=0;i<edge[u].size();i++)
	{
		int p=edge[u][i];
		if(p!=v)
		{
			dfs(p,u);
		}
	}
	
}
int lca(int a,int b)
{
	if(dep[a]<dep[b]) swap(a,b);
	while(dep[a]!=dep[b])
	{
		a=fa[a];
	}
	if(a==b) return a;
	while(a!=b)
	{
		a=fa[a];
		b=fa[b];
	}
	return a;
}
int main()
{	 
	int n,m,s,x,y;
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d",&x,&y);
		edge[x].push_back(y);
		edge[y].push_back(x);
	}
	dfs(s,0);
	int a,b;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&a,&b);
		printf("%d\n",lca(a,b));
	}
	return 0;
 } 

在这里插入图片描述
再用倍增的思路来想
1.因为这是一步一步的走 我们能不能优化一下呢 能不能一步多走点
这个时候我们就需要用到这个数组f[i][j] 它表示的意思是结点 i向上爬了2^j次下所在的结点
比如上图那个树来说 f[2][0]=4 f[1][0]=4 f[3][1]=4 f[5][1]=4

2.对于数组f[i][j]来讲它的公式可以这样写f[i][j]=f[i][f[i][j-1]][j-1]
为什么这样写呢 f[i][j-1]的意思就是结点i跳了2^j-1下后所在的结点 那么它再从此结点跳2^j-1下 是不是就相当于从i跳了2^j下?
因为 2^j== (2^(j-1))*2 所有就有了这个递推公式f[i][j]=f[i][f[i][j-1]][j-1]
3.下面我们来看这个倍增法每部分代码的含义
①dfs(u,v)其实也就是预处理 求最近公共祖先前 你需要知道每个结点的深度以及每个结点跳2^j次下后它的祖先
②lca(a,b)就是用倍增法找任意俩个结点a,b的最近公共祖先
首先我们需要做的是让这两个结点爬的同一深度(其实就是让深度最深的那个结点往上爬)

可能又有人问了 为什么循环的时候是从29往0循环呢 而不是从0往29循环呢 我们都知道任何一个正数我们都可以用二进制来表示 那么这两者之间的深度差是不是也可以用二进制来表示?
就拿5来说 5是不是可以表示为101
如果我们从0到29这样循环 那这个2^1这个地方你跳不跳?如果你跳的话 我们就不能到达同一个深度
但是如果我们从29到0这样循环的话我们就可以先跳一个2^2
再跳一个2^0 其中那个2^1就不需要跳了就可以到达同一个深度 你可以自己在心里想想这个地方 为啥要逆着来的

当两个结点再同一个高度的时候 可以先比较两者是不是同一个结点 如果是就直接输出 如果不是的话 两者一起再往上跳直到跳的最近公共祖先的孩子结点时候就停止 然后输出他们的父节点进行了也就是f[a][0]
可能这个地方又有人问了 为啥非得跳到最近公共祖先的孩子结点呢 为啥不直接跳到最近公共祖先啊 我认为如果让他俩之间都跳到公共祖先的时候 有可能不是最近公共祖先 有可能直接跳的更远了跳过了 我认为这个地方其实跟上面那个二进制那有点相似自己多想想应该没问题

AC代码2倍增

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+7;
int dep[maxn];//结点深度
int f[maxn][30];//f[i][j]结点i跳2^j次后所在结点
ll bit[30];//bit[i]代表为2^i次方
int n,m;
vector<int>edge[maxn];
void init()
{
	bit[0]=1;
	for(int i=1;i<=29;i++)
	{
		bit[i]=bit[i-1]<<1;
	}
}
void dfs(int u,int v)//预处理 
{
	dep[u]=dep[v]+1;
	f[u][0]=v;
	for(int i=1;bit[i]<=dep[u];i++)
	{
		f[u][i]=f[f[u][i-1]][i-1];
	}
	for(int i=0;i<edge[u].size();i++)
	{
		int p=edge[u][i];
		if(p!=v)
		{
			dfs(p,u);
		}
	}
}
int lca(int a,int b)
{
	if(dep[a]<dep[b]) swap(a,b);
	for(int i=29;i>=0;i--)
	{
		if(dep[a]-dep[b]>=bit[i])
		{
			a=f[a][i];
		}
	}
	if(a==b) return a;
	for(int i=29;i>=0;i--)
	{
		if(f[a][i]!=f[b][i])//如果让他俩相等的话有可能就不是最近公共祖先了
		{
			a=f[a][i];
			b=f[b][i];
		}
	}
	return f[a][0];
}
int main()
{
	int n,m,s,x,y;
	memset(dep,0,sizeof(dep));
	init();
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d",&x,&y);
		edge[x].push_back(y);
		edge[y].push_back(x);
	}
	dfs(s,0);
	int a,b;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&a,&b);
		printf("%d\n",lca(a,b));
	}
	return 0;
 } 

在这里插入图片描述
两者比一下还是倍增法速度比较快 如果思路不正确可以指出来 代码也不知道咋过了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值