学习LCA前提须知
LCA是在一棵树上求两个点的最近公共祖先。两个点共同能到达的点,这样的点我们称它为公共祖先,那么两个点共同能到达的第一个点,这样的点我们称它为最近公共祖先
算法内容
前置技能
您需要去了解 邻接表存图 倍增算法基本原理 高中必修一log函数计算
竞赛需要用到的点
1、LCA常作为思维题的工具,不单独考
2、倍增LCA必加优化,或者可以选择常数更优秀的树剖求LCA
倍增求LCA略讲
考虑常规算法求LCA,我们需要知道当前查找的两个点是否遍历过了同一个点上,若一旦有被两点都遍历到的点,那么当前点就是我们的最近公共祖先。我们现在的问题就是,如何找到这样的点?考虑从路径入手,我们一开始的朴素算法能够想到的就是一个一个往上走,但实际上走的很多点都不会用到,我们考虑一个二分的逆运算,倍增
我们从倍增入手,倍增的基本原理就是,从某一个点(数)出发,进行二的次幂级别的运动,并且判断当前是否满足条件,若不满足再次进行跳跃,每一次跳跃都是上一次跳跃路径长度的 2 倍。那就这样跳?肯定是不行的,因为你不知道何时停止,如果不加限制条件,那么就可能判断整棵树的根节点为它们的最近公共祖先,这肯定是错误的,那如何加限制条件呢?
我们现在的目标很明确,就是用倍增向上跳找到最近公共祖先,那我们应该怎样加限制条件呢?我们可以很轻松得到,当它们在同一深度时,若它们的节点不同,那么肯定是还没到达任何一个公共祖先,那么当我们满足 当前两点同深度,不同点并且不能够再往上跳 的时候,是不是意味着再往上走一个点就是我们的最终答案了呢?答案是肯定的。
那我们就可以开始写代码了
部分代码展现
首先是设变量和邻接表
//#define fre yes
#include <cstdio>
#include <cstring> // memset
const int N = 100005;
int head[N << 1], to[N << 1], ver[N << 1];
int depth[N], f[N][22], lg[N];
// depth代表深度 设f[x][k]的话就是 x点向上走2^k步
// lg就是我们的优化
int tot;
void addedge(int x, int y) {
ver[tot] = y;
to[tot] = head[x];
head[x] = tot++;
}
int main() {
memset(head, -1, sizeof(head));
...
}
然后我们需要来一个先将我们的depth数组和f数组起始化的函数,并且加上我们的优化
void dfs(int x, int fa) { //x为当前节点 fa为x的上一个节点
depth[x] = depth[fa] + 1;
f[x][0] = fa;
for (int i = 1; (1 << i) <= depth[x]; i++) {
f[x][i] = f[f[x][i - 1]][i - 1];
//这里是一个推导公式
//x向上走2^i 相当于x向上走2^{i - 1} + 2^{i - 1}
}
for (int i = head[x]; ~i; i = to[i]) {
int v = ver[i];
if(v != fa) {
dfs(v, x);
}
}
}
int main() {
static int n; //n个点
...
for (int i = 1; i <= n; i++) {
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
}
dfs(root, -1);
...
}
起始化了之后就可以求我们的LCA了
#include <iostream>
int lca(int x, int y) {
if(depth[x] < depth[y]) {
std::swap(x, y);
}
while(depth[x] > depth[y]) {
x = f[x][lg[depth[x] - depth[y]] - 1];
//这样就能让x能跑到y的深度
}
if(x == y) return x; //如果直接相等了 那么x肯定是的最近公共祖先
for (int k = lg[depth[x]] - 1; i >= 0; i--) {
//这里也是一句优化 即跑到顶最少需要多少次2^k
if(fa[x][k] != fa[y][k]) {
//如果不相等 那么满足条件 向上跳
x = fa[x][k];
y = fa[y][k];
}
} return fa[x][0];
}
完整代码
//#define fre yes
#include <cstdio>
#include <cstring>
#include <iostream>
const int N = 500005;
int head[N << 1], ver[N << 1], to[N << 1];
int lg[N], depth[N], f[N][22];
int tot;
void addedge(int x, int y) {
ver[tot] = y;
to[tot] = head[x];
head[x] = tot++;
}
void dfs(int x, int fa) {
depth[x] = depth[fa] + 1;
f[x][0] = fa;
for (int i = 1; (1 << i) <= depth[x]; i++) {
f[x][i] = f[f[x][i - 1]][i - 1];
}
for (int i = head[x]; ~i; i = to[i]) {
int v = ver[i];
if(v != fa) {
dfs(v, x);
}
}
}
int lca(int x, int y) {
if(depth[x] < depth[y]) {
std::swap(x, y);
}
while(depth[x] > depth[y]) {
x = f[x][lg[depth[x] - depth[y]] - 1];
}
if(x == y) return x;
for (int k = lg[depth[x]] - 1; k >= 0; k--) {
if(f[x][k] != f[y][k]) {
x = f[x][k]; y = f[y][k];
}
} return f[x][0];
}
int main() {
memset(head, -1, sizeof(head));
static int n, m, s;
scanf("%d %d %d", &n, &m, &s);
for (int i = 1; i < n; i++) {
int x, y;
scanf("%d %d", &x, &y);
addedge(x, y);
addedge(y, x);
}
for (int i = 1; i <= n; i++) {
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
} dfs(s, 0);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d %d", &x, &y);
printf("%d
", lca(x, y));
} return 0;
}