终于看懂LCA倍增法,所以想要分享一下(本人是学y总的做法)
LCA---即找两个结点最近公共祖先
那么先来看一道题吧
这是洛谷一道LCA模板题
首先我们需要fa[N][20],depth[N],p[N]数组,fa数组是用来倍增的,也就是存储该结点的祖先(包括它的父亲和爷爷),depth数组是存储当前结点的深度,我们把根节点的depth定为1,p数组用来模拟队列
那么现在我们先把前文以及main函数部分给出,方便后续理解
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N = 5e5 + 10;
int n, m, s;
vector<int> edges[N];
int fa[N][21];
int depth[N];
int q[N]; // 固定大小队列
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(fa, 0, sizeof fa);
cin >> n >> m >> s;
for (int i = 1; i < n; i++) {
int x, y;
cin >> x >> y;
edges[x].push_back(y);
edges[y].push_back(x);
}
bfs();
while (m--) {
int a, b;
cin >> a >> b;
cout << lca(a, b) << '\n';
}
return 0;
}
接下来我们顺着main函数步骤讲解,首先是bfs部分
void bfs() {
memset(depth, 0x3f, sizeof depth);
depth[0] = 0; // 哨兵节点
depth[s] = 1; // 根节点深度为1
int hh = 0, tt = 0;
q[tt++] = s; // 初始队列为根节点
while (hh < tt) { // 正确终止条件
int u = q[hh++];
for (int v : edges[u]) {
if (depth[v] > depth[u] + 1) {
depth[v] = depth[u] + 1;
fa[v][0] = u;
// 递推祖先数组
for (int k = 1; k <= 20; k++) {
fa[v][k] = fa[fa[v][k - 1]][k - 1];
}
q[tt++] = v; // 仅在更新后入队
}
}
}
}
这个递推祖先数组就是把当前结点的祖先都存放在fa数组,利用了二进制的思想
主要是lca部分
int lca(int a, int b) {
if (depth[a] < depth[b]) swap(a, b);
// 对齐深度
for (int k = 20; k >= 0; k--) {
if (depth[fa[a][k]] >= depth[b]) {
a = fa[a][k];
}
}
if (a == b) return a;
// 同步跳跃
for (int k = 20; k >= 0; k--) {
if (fa[a][k] != fa[b][k]) {
a = fa[a][k];
b = fa[b][k];
}
}
return fa[a][0];
}
我们来个图讲解一下吧
首先是对齐深度
红色代表结点深度,那么对齐深度功能就是通过fa数组将9不断找祖先,知道祖先位置与7的深度对齐,那么此时9就会找到3
然后同步跳跃部分,你可能会疑惑为什么我上面这个深度对齐了,那直接返回他们两的上一个结点不就行了,其实上面的图只是个特例
比如这张图我要找9和10的祖先,那么先对齐深度,9结点变成5结点,那么发现这两个的父节点不相同,那么此时同步跳跃就发力了,他会将10结点和5结点同步跳跃,也就是每次执行他两深度不变,直到找到最近祖先2,那么就停止
好了,感谢观看,虽然本人写这篇博客是码性大发,哈哈哈,就当加深理解了