网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 后端开发 > C++ >

最近公共祖先模板

时间:2025-04-05 18:53

人气:

作者:admin

标签:

导读:最近公共祖先 题目:https://acm.hdu.edu.cn/showproblem.php?pid=2586 #include lt;bits/stdc.hgt; using u32 = unsigned; using i64 = long long; using u64 = unsi...

最近公共祖先

题目:https://acm.hdu.edu.cn/showproblem.php?pid=2586

#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
//
const int N = 50010;
int f[N][20], d[N], dist[N];
int e[2*N], ne[2*N], w[2*N], h[2*N], idx;
int n, m, t;
void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
std::queue<int> q;
void bfs() {
	q.push(1); // 加入根节点
	d[1] = 1; // 根节点的层次为1
	while (q.size()) {
		int x = q.front();
		q.pop();
		for (int i = h[x]; i != -1; i = ne[i]) {
			int y = e[i];
			if (d[y] != -1) continue; // 当前y已经遍历到了,并且已经给深度赋值了
			d[y] = d[x] + 1; // y的层次等于x的层次加一
			dist[y] = dist[x] + w[i]; // y到根节点的距离为x到根节点的距离加上边(x,y)的大小
			f[y][0] = x; // y向上走2的零次方步到达x
			for (int j = 1; j <= t; j ++) {
				f[y][j] = f[f[y][j-1]][j-1]; // y向上走2^j步到达的点就是:y向上走2^(j-1)步到达的点,再向上走2^(j-1)步到达的点
			}
			q.push(y); // 加入队列
		}
	}
}

int lca(int x, int y) {
	if (d[x] < d[y]) std::swap(x, y); // 使得x的层次大于y的层次
	for (int i = t; i >= 0; i --) {
		if (d[f[x][i]] >= d[y]) {
			// x向上走2^i次方步都还是比y的深度大,那么还得继续走
			x = f[x][i];
		}
	}
	if (x == y) return y; // 走完之后,x与y相等,那么y就是最近公共祖先,因为y的深度比较大,所以y应该是祖先
	// 否则的话,这两个点的深度应该是相等的
	for (int i = t; i >= 0; i --) {
		// 同时往上移动
		if (f[x][i] != f[y][i]) {
			x = f[x][i];
			y = f[y][i];
		}
	}
	return f[x][0]; // 最终这两个点的父节点就是最近公共祖先
}

int main()
{
	int T;
	std::cin >> T;
	while (T --) {
		std::cin >> n >> m;
		t = (int)(log(n) / log(2)) + 1;
		// 清空
		memset(h, -1, sizeof h);
		memset(d, -1, sizeof d);
		idx = 0;
		// 读入一棵树
		for (int i = 1; i < n; i ++) {
			int x, y, z;
			std::cin >> x >> y >> z;
			add(x, y, z);
			add(y, x, z);
		}
		bfs(); // 预处理信息
		// 回答问题
		for (int i = 1; i <= m; i ++) {
			int x, y;
			std::cin >> x >> y;
			std::cout << dist[x] + dist[y] - 2 * dist[lca(x, y)] << "\n"; // 两个点的距离为两个点到根节点的距离之和减去2倍最近公共祖先到根节点的距离
		}
	}
	return 0;
}

其中几个重要的点
1、t:叶子节点到达根节点的最长路径(2的多少次方)
t = (int)(std::log(n) / std::log(2)) + 1;
2、f[i][j]: 从点i向上走2^j次方步能到达的点,比如f[i][0] = father[i](向上走2^0步到达父节点)
遍历当前节点x的子节点,用y表示,则f[y][j]的求法如下:

f[y][0] = x;
for (int j = 1; j <= t; j ++) {
    f[y][j] = f[f[y][j-1]][j-1]; // y向上走2^j步到达的点就是:y向上走2^(j-1)步到达的点,再向上走2^(j-1)步到达的点
}

3、d[x]:点x的深度,其父节点用px表示
初始化d数组为-1,用于判断是否当前点被遍历到。若当前点没被遍历到,则d[x] = d[px] + 1;
4、dist[x]:点x到根节点的距离,其父节点用px表示,边(px, x)的权值用w[i]表示
初始化d数组为0,若当前点还没有被遍历到,则dist[x] = dist[px] + w[i];
5、求x与y的最近公共祖先:
若x的深度小于y的深度,交换x与y,统一为x的深度大于等于y的深度

for (int i = t; i >= 0; i --) {
	if (d[f[x][i]] >= d[y]) {
		// x向上走2^i次方步都还是比y的深度大,那么还得继续走
		x = f[x][i];
	}
}

执行上述代码之后,x与y的深度可能相同,或者x等于y(y是x的祖先)
-------1)若x与y相等,那么直接返回y,y就是最近公共祖先
-------2)否则,x与y的深度相同,一同向上走

for (int i = t; i >= 0; i --) {
	// 同时往上移动
	if (f[x][i] != f[y][i]) {
	    x = f[x][i];
		y = f[y][i];
	}
}

此时结束之后,应满足向上2^i次方步他们相等
最后x的父节点f[x][0]就是答案

本文来自博客园,作者:来杯whiskey,转载请注明原文链接:https://www.cnblogs.com/zj-cnbolgs/p/18810364

上一篇:

下一篇:

温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信