「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]);
}
posted @ 2024-07-30 10:33  卡布叻_周深  阅读(85)  评论(1)    收藏  举报