最近公共祖先
定义
对于两个点,LCA是它们的祖先(或自己)中距离他们最近的点
不妙做法
查询 \(O(n)\)
向上标记
Rt. 一个节点不断往父节点跑,标记节点
另一个节点也是往上跑碰到标记过的就是LCA
向上调整
Rt. 深度深的节点往上调,调到深度一样
如果调成一样了第一个调对的就是LCA,不然往上继续调
树上倍增
预处理 \(O(n\log n)\) 查询 \(O(\log n)\)
珂以理解成向上调整的优化,跟 ST 差不多
预处理
设 \(f(i,j)\) 表示节点 \(i\) 的 \(2^j\) 辈祖先
那么显然要
\[f(i,j)\gets f(f(i-1,j-1),j-1)
\]
记得要初始化 \(f(i,0)\gets fa_i\) 哦
查询
查询 \(x,y\) 的 LCA
不妨令 \(dep_x<dep_y\)
那先把 \(y\) 调到 \(x\) 的深度,之后二进制同步调搞出 LCA 即可
具体而言,就是从 \(\log n\) 到 \(0\) 循环一遍,如果珂以调(仍不到 \(x\) | 调后不一样) 就调
注意特判 \(x,y\) 已经相同的情况,不然跑不出来
实现
#include<bits/stdc++.h>
#define MN 550000
using namespace std;
typedef long long ll;
int n, m;
int u, v;
int dep[MN];
int f[MN][22], t;
vector<int>to[MN];
void dfs(int p,int fat) {
dep[p]=dep[fat]+1, f[p][0]=fat;
for(int i=0; i<to[p].size(); ++i)
if(to[p][i]!=fat)
dfs(to[p][i],p);
}
void pwork() {
dfs(1,0); t=log2(n);
for(int i=1; i<=t; ++i)
for(int p=1; p<=n; ++p)
f[p][i]=f[f[p][i-1]][i-1];
}
int LCA(int x,int y) {
if(dep[x]>dep[y]) swap(x,y);
for(int i=t; i>=0; --i)
if(dep[f[y][i]]>=dep[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];
if(u==v) return u;
return f[x][0];
}
int main() {
scanf("%d%d", &n, &m);
for(int i=1; i<n; ++i) {
scanf("%d%d", &u, &v);
to[u].push_back(v);
to[v].push_back(u);
}
pwork();
while(m--) {
scanf("%d%d", &u, &v);
printf("%d\n", LCA(u,v));
}
return 0;
}
Trajan 獭酱
处理 \(O(n+m)\) ,非常优秀,可惜了
离线,珂以理解成向上标记的优化
总之遍历然后自下而上建立并查集
但是这玩意因为带了个并查集所以常数巨大