倍增求LCA(最近公共祖先)

定义

LCA(Least Common Ancestors),最近公共祖先,如节点 z z z x , y x,y xy的祖先,则称 z z z x , y x,y x,y的公共祖先,深度最最大的公共祖先称为 L C A LCA LCA
比如说:
在这里插入图片描述
L C A LCA LCA(4,5)=2, L C A LCA LCA(5,6)=1, L C A LCA LCA(2,3)=1。

懂了?

如何求LCA

暴力方法很容易得到。
我们用 d f s dfs dfs求出点的深度 d d d
只要每次将自己的深度赋为自己父亲的深度+1即可。

然后深度大的先一步一步跳,跳的与另一个节点深度一样。
然后……两个一起跳,跳到一起,当前节点就是 L C A LCA LCA

但是万一这个距离有点大,一步步跳超时怎么办?

正题:倍增法

我们设 f [ i ] [ j ] f[i][j] f[i][j]代表i号节点的 2 j 2^j 2j辈祖先(就是往上爬 2 j 2^j 2j步)。
如果没有这个祖先(爬过根节点了)就设为0(不存在)。

很容易得到一条等式
f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] f[i][j]=f[f[i][j-1]][j-1] f[i][j]=f[f[i][j1]][j1]
因为 i i i往上爬 2 j − 1 2^j-1 2j1再爬 2 j − 1 2^j-1 2j1就是直接往上爬 2 j 2^j 2j步。
还有 f [ i ] [ 0 ] f[i][0] f[i][0]就是自己的父节点(往上走一步就是自己的节点)

以上是预处理。

实现

void dg(int u,int father)//u开始为根节点,father开始为0(根节点的父节点不存在)
{
	int i;
	d[u]=d[father]+1;//深度是自己父亲加1
	for (i=0;i<=19;i++) 
		f[u][i+1]=f[f[u][i]][i];//当前节点的f数组更新(此时f[u,i]已知)
	for (i=head[u];i;i=next[i])//枚举出边
	{
		if (e[i]==father) continue;//因为是无向图,所以无向存图,会建立反向边,
								   //指向父节点,就要判断是否指向父节点
		f[e[i]][0]=u;//子节点往上跳2^0=1步就是自己
		dg(e[i],u);//递归子树
	}	
}

那么自然还要求 L C A LCA LCA是吧。

设要求 L C A LCA LCA x x x, y y y)(使得 d [ x ] d[x] d[x]> d [ y ] d[y] d[y],否则可交换)
算法流程大概是这样:
先把 x x x跳到和 y y y同深度
跳2logn步……, 2 1 2^1 21步, 2 0 2^0 20步。
然后两个一起……
跳2logn步……, 2 1 2^1 21步, 2 0 2^0 20步。
跳到了!

int lca(int x,int y)
{
	int i;
	if (d[x]<d[y]) swap(x,y);//使深度保证d[x]>=d[y]
	for (i=20;i>=0;i--)
	{
		if (d[f[x][i]]>=d[y])//保证没跳过头 
			x=f[x][i];//跳
		if (x==y) return x;//万一跳到同一层就重合了,直接退出
	}
	for (i=20;i>=0;i--)
	{
		if (f[x][i]!=f[y][i])//两点一起跳还没重合
		{
			x=f[x][i];//跳
			y=f[y][i];
		}
	}
	return f[x][0];//两点还没重合,只是跳完了,它们共有的父节点就是LCA
}

谢谢,有疑问私聊。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值