关于 LCA 的简单记录
最近做了几道 LCA 的题目。所以总结一下。
首先我们来回顾一下倍增求 LCA 的流程吧。
首先是初始化:
- 进行 bfs。
- 处理出每层的深度。
- 处理每个节点的
级父亲,方式为一个递推,即为由 级祖先的 祖先推出 级祖先。
然后是每次的查询:
- 把 y 的高度提到和 x 相同。运用向上提
级的方法,如果不超过 x 的话就继续提。 - 对于
从高到低尝试,如果 x 和 y 的第 级祖先不同的话,那么把这二者同时向上提 级,根据二进制分解的知识,我们知道用这种方法能向上提任意的数。那么这会使得二者尽可能向上提,使得二者仅差一步就能相会。 - 那么仅差一步的话,直接返回任意一个的父亲就好了。
简单例题:聚会
Y 岛风景美丽宜人,气候温和,物产丰富。Y 岛上有
题意概括:3 个点在树上,求使这 3 个点联通的树上最短路径。
解法
这道题简单到飞,我们发现可以仅仅先对 x 和 y 求 lca,然后便可以对于 z 进行分类讨论。
z 的情况分为两大类:
- 是 x 和 y 的 lca 的祖先或者和 x 和 y 的 lca 没有亲辈和子辈关系
- x、 y、 z 的公共 lca 是 x 和 y 的 lca
其中第一种情况的解决方案是,集合地点为 x 和 y 的 lca,然后再计算 z 到 lca 的距离,求总和。
而第二种也分两大类,路径为一条线或者带有一个分叉,即为:
- z 为 x 或者 y 的祖先
- z 既非 x 的祖先也非 y 的祖先
前者的解决方案是,集合地点为 z 点,距离为 x 和 y 的距离。
而带有分叉的情况,即后者,若和 x 的 lca 的深度深于和 y 的,那么集合地点选在和 x 的 lca 处,距离为 x 和 y 的距离加 z 到和 x 的 lca 的距离。反之亦然。
那么代码就容易写出了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 5e5 + 5;
struct E {
int nxt, to;
} e[maxn << 1];
int n, p, cnt, head[maxn], t;
int f[maxn][23], d[maxn];
queue<int> q;
void add(int a, int b) {
e[++ cnt].to = b;
e[cnt].nxt = head[a];
head[a] = cnt;
}
void bfs() {
q.push(1);
d[1] = 1;
while(q.size()) {
int x = q.front();
q.pop();
for(int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(d[y]) continue;
d[y] = d[x] + 1;
f[y][0] = x;
for(int j = 1; j <= t; j ++)
f[y][j] = f[f[y][j - 1]][j - 1];
q.push(y);
}
}
}
int lca(int x, int y) {
if(d[x] > d[y]) swap(x, y);
for(int i = t; i >= 0; i --)
if(d[f[y][i]] >= d[x])
y = f[y][i];
if(x == y) return x;
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() {
scanf("%d%d", &n, &p);
t = (int) (log(n) / log(2)) + 1;
for(int i = 1, u, v; i < n; i ++) {
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
bfs();
for(int i = 1, x, y, z; i <= p; i ++) {
scanf("%d%d%d", &x, &y, &z);
int lcaxy = lca(x, y);
int lcaxyz = lca(lcaxy, z);
if(lcaxyz != lcaxy && lcaxyz != z) {
printf("%d %d\n", lcaxy, d[x] + d[y] - d[lcaxy] + d[z] - 2 * d[lcaxyz]);
} else if(lcaxyz == z) {
printf("%d %d\n", lcaxy, d[x] + d[y] - d[lcaxy] + d[z] - 2 * d[lcaxyz]);
} else if (lcaxyz == lcaxy) {
int lcaxz = lca(x, z), lcayz = lca(y, z);
if(lcaxz == z || lcayz == z) {
printf("%d %d\n", z, d[x] + d[y] - 2 * d[lcaxy]);
} else {
printf("%d %d\n", d[lcaxz] > d[lcayz] ? lcaxz : lcayz,
d[lcaxz] > d[lcayz] ? d[x] + d[y] - 2 * d[lcaxy] + d[z] - d[lcaxz] : d[x] + d[y] - 2 * d[lcaxy] + d[z] - d[lcayz]);
}
}
}
}
简单例题2:[USACO15DEC] Max Flow P
FJ 给他的牛棚的
FJ 有
解法
这题是一个典型的树上差分问题。具体来讲是先进行倍增 lca 的处理。然后对于每个点的值进行记录。
采用的是经典的差分思想:通过计算差分数组的前缀和即可推断出原数组。
对于每条线路
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 5e4 + 5;
int n, k;
struct E {
int nxt, to;
} e[maxn << 1];
int cnt, head[maxn], f[maxn][23], d[maxn], t, p[maxn];
int ans;
queue<int> q;
void add(int u, int v) {
e[++ cnt].to = v;
e[cnt].nxt = head[u];
head[u] = cnt;
}
void bfs() {
q.push(1);
d[1] = 1;
while(q.size()) {
int x = q.front();
q.pop();
for(int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(d[y]) continue;
d[y] = d[x] + 1;
f[y][0] = x;
for(int j = 1; j <= t; j ++)
f[y][j] = f[f[y][j - 1]][j - 1];
q.push(y);
}
}
}
int lca(int x, int y) {
if(d[x] > d[y]) swap(x, y);
for(int i = t; i >= 0; i --)
if(d[f[y][i]] >= d[x])
y = f[y][i];
if(x == y) return x;
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];
}
void calc(int x, int fa) {
for(int i = head[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(y == fa) continue;
calc(y, x);
p[x] += p[y];
}
ans = max(ans, p[x]);
}
int main() {
cin >> n >> k;
t = (int) (log(n) / log(2)) + 1;
for(int i = 1, x, y; i < n; i ++) {
cin >> x >> y;
add(x, y), add(y, x);
}
bfs();
for(int i = 1, a, b; i <= k; i ++) {
cin >> a >> b;
p[a] ++, p[b] ++, p[lca(a, b)] --, p[f[lca(a, b)][0]] --;
}
calc(1, 0);
cout << ans << endl;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】