最近公共祖先模板

最近公共祖先

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

cpp 复制代码
#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]的求法如下:

cpp 复制代码
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的深度

cpp 复制代码
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的深度相同,一同向上走

cpp 复制代码
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]就是答案