最近公共祖先
题目: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]就是答案