「dfs 序求 lca」学习笔记
一种 \(O(n\log n)\) 预处理,\(O(1)\) 查询 lca 的算法,类似于压缩版的欧拉序求 lca 做法,但时空复杂度更优。
对于两点 \(x,y\),不妨设 \(dfn_x<dfn_y\)。
- 若 \(x=y\),\(lca(x,y)=x\),这个需要特判。
- 若 \(x,y\) 在 \(lca\) 的两棵不同子树上,从 \(x\to y\) 的路径就是 \(x\to lca\) 再 \(lca\to y\),发现 \(lca\to y\) 这一条路径上除了 \(lca\) 其余节点 \(i\) 的 \(dfn\) 均满足 \(dfn_x\le dfn_i\le dfn_y\),故此直接求 \(dfn_i\) 在区间 \([dfn_x,dfn_y]\) 内的节点 \(i\) 中 \(dep_i\) 最小的那个点,其父亲就是 \(lca(x,y)\)。
- 若 \(y\) 在 \(x\) 的子树上,\(lca(x,y)=x\),其实可以特判,但不简洁,不妨取 \(x'\) 满足 \(dfn_{x'}=dfn_x+1\),再按照上述过程处理,答案依旧正确。
最后发现使 \(dfn_{x'}=dfn_x+1\) 的操作对于第二种情况对答案也没有影响,故此是一种通解。
上述操作可以用 \(ST\) 表维护。
该方法对于大多数题尤其询问时不允许多个 \(\log\) 的情况下十分优秀,但由于树剖常数巨小,非特殊情况用树剖求 \(lca\) 也是一种不错的选择。
- 附录:c++ 库内自带函数
__lg(n)为 \(O(1)\),而log2(n)复杂度为 \(\log_2 n\)。
void dfs(int x,int fa)
{
dfn[x]=++tot; mi[tot][0]=fa;
for(int y:e[x]) if(y!=fa) dfs(y,x);
}
int min_(int x,int y) {return dfn[x]<dfn[y]?x:y;}
void ST()
{
for(int j=1;j<=__lg(n);j++)
for(int i=1;i+(1<<j)-1<=n;i++)
mi[i][j]=min_(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}
int lca(int x,int y)
{
if(x==y) return x;
x=dfn[x],y=dfn[y];
if(x>y) swap(x,y);
x++; int t=__lg(y-x+1);
return min_(mi[x][t],mi[y-(1<<t)+1][t]);
}

浙公网安备 33010602011771号