LCA的暴力优化(倍增方式)

文章借鉴自:算法系列——最近公共祖先LCA(暴力,倍增算法,Tarjan) - 白菜茄子 - 博客园 (cnblogs.com)icon-default.png?t=N7T8https://www.cnblogs.com/sunjianzhao/p/12769284.html#_label0

LCA其实就是树上面两个节点的的最近公共祖先。也就是说最直接暴力的解法就是将这两点先调整的统一位置,然后同时向上找祖先,如果某个高度的祖先相等了,那么就找到了。

暴力解法:

代码:

int GetLCA(int x,int y){

  if(dep[x]>dep[y]) //令y为更深

  swap(x,y);

  while(dep[x]<dep[y])

    y=fa[y];//让更深的y先往上跳,直到与x相同的高度

  while(x!=y) //两者同时上跳,直到重合

    x=fa[x],y=fa[y];

return x;

}

倍增优化:

从暴力的解法里面我们发现关键在于两个节点同时向上跳跃,从而寻找到最近的公共祖先。那么对于其中的跳跃如果一次性可以跳的更远的话就可以节省很多时间。所以 我们使用倍增来记录某个点跳跃的距离为1,2,4,8等距离的位置。在跳跃的时候只要从最大的距离开始跳然后逐步缩小就可以跳到最近的公共祖先的下一个位置。然后返回这个位置的父节点即可。

对于如何建立合适的树:

题目中所给的只是哪两个点之间有路径,那么我们可以对于路径记录从x到y和从y到x,由于题目中给出了根节点,从而从根节点进行DFS向小就可以建立起父子关系的树以及每一个节点的高度。或许会担心会不会遍历的时候返回到父节点,这个问题只需要在递归过程中传递一个y作为父节点。在遍历每个临界点的下一个如果是y就直接跳过即可。

模板题目:

P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3379

AC代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 500000+10;
int N,M,S;
vector<int> vt[maxn];
int fa[maxn][20];//记录倍增,也就是某一个节点向上跳跃1,2,4,8,10等等的节点。
int maxdepth;//数的最大深度
int depth[maxn];//各节点在数上的深度
//构造我们所需要的树。其中y可以控制不遍历回父亲节点。 
void dfs(int x, int y) {
	//从根节点向下找寻,找寻最大深度以及各个节点的数上的深度
	for (int i=0;i<vt[x].size();i++) {
		if (vt[x][i]==y) continue;
		//记录位置 
		depth[vt[x][i]] = depth[x]+1;
		//记录最深的深度 
		maxdepth = max(maxdepth, depth[vt[x][i]]);
		fa[vt[x][i]][0] = x;//向上一步的跳到的节点
//		printf("fa[%d][%d]=%d\n", vt[x][i], 0, fa[vt[x][i]][0]);
		dfs(vt[x][i], x);
	}
}

//进行倍增的预处理
void init(int x) {
	for (int i=0;i<20;i++) {
		fa[x][i] = 0;//限制一个最大的跳跃 
	}
	for (int i=1;(1<<i)<=maxdepth;i++) {
		for (int j=1;j<=N;j++) {
			if (j==x) continue;
			//倍增跳跃,其实就是比起上一步的跳跃连跳两步 
			fa[j][i] = fa[fa[j][i-1]][i-1];
//			printf("fa[%d][%d]=%d", j, i, fa[j][i]);
		}
	}
}

//寻找LCA
int find(int x, int y) {
	if (depth[x]>depth[y]) {
		swap(x, y);
	}
	while (depth[y]>depth[x]) {
		y = fa[y][int(log(depth[y]-depth[x])/log(2))];
	}
//	printf("x=%d y=%d\n", depth[x], depth[y]);
	if (x==y) {
		return x;
	}
	for (int i=(int(log(depth[y])/log(2)));i>=0;i--) {
		if (fa[x][i]!=fa[y][i]) {
			x = fa[x][i];
			y = fa[y][i];
		}
		
	}
	return fa[x][0];
}
 

int main() {
	cin>>N>>M>>S;
	int x, y;
	//读入题目所给的路径
	for (int i=0;i<N-1;i++) {
		cin>>x>>y;
		vt[x].push_back(y);
		vt[y].push_back(x); 
	}
	depth[S] = 0;//让根节点的深度为0
	//建立父子关系的树
	dfs(S, -1);
	init (S);
	int a, b;
	while (M--) {
		cin>>a>>b;
		cout<<find(a, b)<<endl;
	}
	
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值