倍增求lca

倍增求lca 模板
求最近公共祖先lca(Least Common Ancestors),什么是公共祖先,给定一棵树,若节点z即使节点x的祖先,也是节点y的祖先,则称z是x,y的公共祖先,在x,y的所有公共祖先中,深度最大的节点称x,y的最近公共祖先,记做LCA(x,y)。
举个例子
在这里插入图片描述
LCA(4,5) = 2,LCA(5,6)=1,LCA(2,3)=1;
那么如何求LCA
先给出一张我们接下来要讲的例子:
例子
第一:暴力求法
先暴力dfs出每个点的深度,假设以 171717和 18 为例,既然要求LCA,那么我们就让他们一个一个向上跳,直到相遇为止。第一次相遇即是他们的LCA。 模拟一下就是:
17−>14−>10−>7−>3
18−>16−>12−>8−>5−>3
最终结果就是 3
但是这个算法的时间复杂度却是很高,所以我们采取了第二种重要的方法。
树上倍增法
设pre[x][k]表示x的2k 辈祖先,即从x向根节点走2k步到达的节点,若该节点不存在,则令pre[x][k] = 0,f[x][0]就是x的父节点。
所谓倍增,就是按2的倍数来增大,也就是跳 1,2,4,8,16,32 …… 不过在这我们不是按从小到大跳,而是从大向小跳,即按……32,16,8,4,2,1来跳,如果大的跳不过去,再把它调小。这是因为从小开始跳,可能会出现“悔棋”的现象。拿 5为例,从小向跳,5≠1+2+4,所以我们还要回溯一步,然后才能得出5=1+4;而从大向小跳,直接可以得出5=4+1。这也可以拿二进制为例,5(101),从高位向低位填很简单,如果填了这位之后比原数大了,那我就不填,这个过程是很好操作的。
还是以 17 和 18 为例(此例只演示倍增,并不是倍增LCA算法的真正路径):
17−>3
18−>5−>3
算法时间度为O(logn)。
接下来我们拆分算法步骤:
1:设depth[x]表示x的深度,不妨设depth[x]>=depth[y],否则交换x,y;
2:用上述二进制拆分思想,把x向上调整到与y同一高度。
3:若此时x==y,说明lca(x,y)就是x.
4:若此时x!=y,那么继续保持二进制拆分思想,把x,y同时向上调整,并保持深度一致并且二者不会相会。
/我们在跳的时候不能直接跳到它们的LCA,因为这可能会误判,比如4和8,在跳的时候,我们可能会认为1是它们的LCA,但1只是它们的祖先,它们的LCA其实是3。所以我们要跳到它们LCA的下面一层,比如4和8,我们就跳到4和5,然后输出它们的父节点,这样就不会误判了。/
5:此时x,y必定只差一步就会相会了,它们的父节点pre[x][0]就是我们要求的lca。

完整的求17和18的LCA的路径:
17−>10−>7−>3
18−>16−>8−>5−>3
解释:首先,18要跳到和17深度相同,然后18和17一起向上跳,一直跳到LCA的下一层(17是7,18是5),此时LCA就是它们的父亲

在算法实现的过程中,我们可以加上 l o g 2 ( x ) log_2(x) log2(x)的常数时间的优化。
下面给出各个步骤代码。
预处理:

void dfs(int f,int fa){
	depth[f] =  depth[fa] + 1;
	pre[f][0] = fa;
	for(int i=1;(1<<i) <= depth[f];i++)
		pre[f][i] = pre[pre[f][i-1]][i-1];//这个转移可以说是算法的核心之一
                                //意思是f的2^i祖先等于f的2^(i-1)祖先的2^(i-1)祖先
                                //2^i=2^(i-1)+2^(i-1)
	for(int i=head[f];i;i=nex[i]){
		if(ver[i] != fa)
		dfs(ver[i],f);
	}
}

查询x,y的lca:

int lca(int x,int y){
	if(depth[x] < depth[y]) swap(x,y); 用数学语言来说就是:不妨设x的深度 >= y的深度
	while(depth[x] > depth[y])
	x = pre[x][lg[depth[x] - depth[y]]-1]; 先跳到同一深度
	
	if(x==y) return x;//如果x是y的祖先,那他们的LCA肯定就是x了
	
	for(int i=lg[depth[x]]-1;i>=0;i--)  //不断向上跳(lg就是之前说的常数优化)
	if(pre[x][i]!=pre[y][i])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过
	x = pre[x][i],y=pre[y][i];
	
	return pre[x][0]; 
}

给出测试链接:https://www.luogu.org/problem/P3379

给出模板:

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+7,M = 1e6+7;
int n,m,s,cnt;
int pre[N][22],depth[N],lg[N];
int head[M],ver[M],nex[M];
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
} 
void read(){
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1,x,y;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
}
void dfs(int f,int fa){
	depth[f] =  depth[fa] + 1;
	pre[f][0] = fa;
	for(int i=1;(1<<i) <= depth[f];i++)
		pre[f][i] = pre[pre[f][i-1]][i-1];//这个转移可以说是算法的核心之一
                                //意思是f的2^i祖先等于f的2^(i-1)祖先的2^(i-1)祖先
                                //2^i=2^(i-1)+2^(i-1)
	for(int i=head[f];i;i=nex[i]){
		if(ver[i] != fa)
		dfs(ver[i],f);
	}
}
int lca(int x,int y){
	if(depth[x] < depth[y]) swap(x,y); 用数学语言来说就是:不妨设x的深度 >= y的深度
	while(depth[x] > depth[y])
	x = pre[x][lg[depth[x] - depth[y]]-1]; 先跳到同一深度
	
	if(x==y) return x;//如果x是y的祖先,那他们的LCA肯定就是x了
	
	for(int i=lg[depth[x]]-1;i>=0;i--)  //不断向上跳(lg就是之前说的常数优化)
	if(pre[x][i]!=pre[y][i])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过
	x = pre[x][i],y=pre[y][i];
	
	return pre[x][0]; 
}
void solve(){
	dfs(s,0);
	for(int i=1;i<=n;i++) 预先算出log_2(i)+1的值,用的时候直接调用就可以了
	lg[i] = lg[i-1]+(1<<lg[i-1]==i);
	
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
}
int main(){
	read();
	solve();
	return 0;
}

资料参考:
https://www.luogu.org/blog/morslin/#
李煜东 算法竞赛进阶指南

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值