求 LCA
一、倍增
预处理 \(O(n\log n)\),单次查询 \(O(\log n)\)。
二、tarjan
狗都不用。询问离线。总共 \(O(n\times a(n) + q)\)。
三、树剖
预处理 \(O(n)\),查询 \(O(\log n)\)。比倍增常数小很多。
int Lca(int x, int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
四、欧拉序列
注意!!!不是即这棵树的括号序列。
而是:递归 \(x\) 开始时,将 \(x\) 加入,每次递归完子树后,回溯时再将 \(x\) 加入。
每个点 1 次,每条边 1 次,所以欧拉序列的长度为 \(n+(n-1)=2n-1\)。
定义【欧拉序】表示一个点在欧拉序列中第一次出现的位置 \(pos(x)\)。
欧拉序有以下两条性质:
-
任意两点简单路径上所有节点均在它们的欧拉序之间出现。
-
任意两点欧拉序之间不会出现它们 LCA 子树外的点。
然后 st 表查询区间里面深度最小值对应的点即可。预处理线性对数,查询常数。
void dfs(int x, int y) {
dep[x] = dep[y] + 1; fa[x] = y, dis[x] = dis[y] + (x <= n);
f[dfn[x] = ++tcnt][0] = x;
for(int to : E[x]) {
if(to == y) continue ;
dfs(to, x);
f[++tcnt][0] = x;
}
}
int get(int x, int y) { return dep[x] < dep[y] ? x : y; }
int Lca(int l, int r) {
if(l == r) return l;
l = dfn[l], r = dfn[r];
if(l > r) swap(l, r);
int k = __lg(r - l + 1);
return get(f[l][k], f[r - (1 << k) + 1][k]);
}