O(1) Dfs 序 LCA

Dfs 序 LCA

普通倍增求 LCA 的时代早已过去!时空大常数并且代码大坨的四毛子树和 Tarjian 的光辉渐渐褪去。新的时代,就要有新的 LCA 求法!dfn 序求 LCA,同时兼备码量巨小,常数(特别是空间常数)巨小,查询 O(1) ,好理解,等众多好处,将 DFS 序求 LCA 发扬光大,让欧拉序求 LCA 成为时代的眼泪!

大量参考:https://www.luogu.com.cn/article/pu52m9ue

假设现在在求 \(u\),\(v\) 的 LCA \(d\),可以发现其在 dfs 序下有许多性质。

不妨让 \(dfn_u < dfn_v\),显然 u 不会是 v 的儿子。可以分类讨论:

  1. u 不是 v 的祖先。那么 dfs 时就是 \(d -> u -> d -> v\) 的顺序,这里虽然经过了 d ,但是显然不会给 d 编号。考虑 d -> v 路径上,只有点 d 不在遍历的过程中标号,假设 d -> v 路径上第二个点为 v',可以发现 v' 是 dfs 序在 \([dfn_u,dfn_v]\) 之间深度最小的点。所以只需要找到 dfs 序在 \([dfn_u,dfn_v]\) 之间深度最小的点 v',它的父亲就是 d。

  2. u 是 v 的祖先。显然可以判断子树的包含关系,但是有一种更加简洁的方法,那就是找到 dfs 序在 \([dfn_{u + 1},dfn_v]\) 之间深度最小的点 v',它的父亲就是 d。这对于情况 1 其实也是适用的。

使用 st 表维护区间深度最小的点,复杂度 \(O(n\log n)~O(1)\)

还要记得特判 u == v 的边界情况。

代码短小精悍:

void dfs(int u, int f) {
    stmin[dfn[u] = ++ DFN][0] = f;
    for(int v : e[u]) if(v ^ f) dfs(v, u);
}
inline int dfnmin(int x, int y) {return dfn[x] < dfn[y] ? x : y;}
void preworkstmin() {For(i, 1, n) for(int j = 1; i - (1 << j) + 1 >= 1; ++j) stmin[i][j] = dfnmin(stmin[i][j - 1], stmin[i - (1 << (j - 1))][j - 1]);} 
inline int LCA(int x, int y) {
    if(x == y) return x;
    if((x = dfn[x]) > (y = dfn[y])) swap(x, y);
    int s = __lg(y - x ++); // y - (x + 1) + 1
    return dfnmin(stmin[x + (1 << s) - 1][s], stmin[y][s]);
}	
int main() {
	n = read(), m = read(), rt = read();
	For(i, 1, n - 1) {
		int u = read(), v = read();
		e[u].pb(v), e[v].pb(u);
	}
	dfs(rt, 0), preworkstmin();
    For(i, 1, m) wt(LCA(read(), read()));
	return 0;
}
posted @ 2025-07-24 19:37  花子の水晶植轮daisuki  阅读(34)  评论(1)    收藏  举报
https://blog-static.cnblogs.com/files/zouwangblog/mouse-click.js