LCA 与树上差分

概述

  • LCA(least common ancestor),最近公共祖先的英文缩写。

  • 顾名思义,LCA 就是树上两个点最近的公共祖先,或者说两个点共同在的极小子树的根。

  • 树上差分则是利用树本身的结合性(显然树不满足差分性,是点到根的链满足差分性)与 LCA 结合做的操作,譬如给某个路径上所有点 \(+x\),转换到差分意义下后等价于路径的左右端点分别 \(+x\),LCA 和 LCA 的父亲处分别 \(-x\)

  • 当然也可以是对边操作之类的,不过具体情况具体分析即可。

求 LCA

tarjan 求 LCA

  • tarjan 求 LCA 是一种离线求 LCA 的方式。主要利用“共同所在极小子树”这一性质,按欧拉环游序处理点的信息,结合并查集求 LCA。

  • 具体来讲,就是从根节点开始 dfs ,进行如下操作:

    • 递归遍历 \(u\) 所有儿子 \(v\)。全部遍历后标记 \(u\) 已被访问过,并将 \(u\) 合并到 \(fa\) 上。

    • 枚举所有要与 \(u\) 求 LCA 的点,如果对应点已访问过,则两点 LCA 为对应点的 \(FA\)(并查集意义下)。

  • 证明:利用欧拉环游序。

    • 可达性:当第二个点开始更新答案的时候,两点同在的极小子树一定还没有被 dfs 完。从而该子树的根还没有出栈,没有合并给它自己的父亲,find 一定会卡在它这里。

    • 必达性:假设 find 到了极小子树根的某个子孙节点,则该节点还没有出栈,故该节点的子树没有 dfs 完,故当前节点也是该子树的节点,该子树才是极小子树。假设不成立。

  • 注意到这里我们的并查集是定向合并的,所以在路径压缩的前提下,最坏情况 \(O(n\log n)\)。tarjan 的 \(O(n\alpha)\) 证明好像是基于树分块的。

  • 给出示范代码(好久之前的东西了)。

int FA[maxn]; bool tarvis[maxn];
il int find(int x){return x==FA[x]?x:FA[x]=find(FA[x]);}
il void tarjan(int now,int fa){
	for(int to:e[now])
		if(to!=fa)
			tarjan(to,now);
	tarvis[now]=1,FA[now]=FA[fa];
	for(qlca qn:ql[now])
		if(tarvis[qn.with])
			p[qn.id].lca=find(qn.with);
	return;
}

倍增求 LCA

  • 倍增求 LCA 是一种预处理每个点的 \(2\) 的整数次幂级祖先后,在线求 LCA 的方式。

  • 建一个倍增数组 \(fa2_{step,x}\) (\(step\) 一般用到 \(20\),因为 \(2^{20}=1048576>10^6\)),表示从 \(x\) 向上走 \(2^{step}\) 步后到哪里。可以认为是一种反边。

  • 具体实现的话,先 dfs 预处理出 \(fa2_{0,x}=fa\)

  • 然后按倍增性质模仿 dp 做一下,转移式:\(fa2_{step,i}=fa2_{step-1,fa2_{step-1,i}}\),即先走 \(2^{step-1}\) 步,再走 \(2^{step-1}\) 步。

  • 询问的时候先让较深的点跳到同深处。如果发现两点已经相同,说明共链,应当直接 return

  • 之后进行倍增二分,从大到小枚举 \(step\),若 \(fa2_{step,u}\neq fa2_{step,v}\),则令 \(u=fa2_{step,u},v=fa2_{step,v}\),可以认为是把需要走的步数二进制拆分了。

  • 最后额外走一步,因为该倍增二分找的其实是最浅的非 lca 点。这里仅给出求 lca 函数的实现,预处理的话不好封装。

int lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    int cha=dep[u]-dep[v];
    foR(i,19,0)
        if(cha&(1<<i)) u=FA[i][u];
    if(u==v) return u;
    foR(i,19,0)
        if(FA[i][u]!=FA[i][v]) u=FA[i][u],v=FA[i][v];
    return FA[0][u];
}
  • 之所以 \(\log\) 在前是因为据说这样比较快(两三倍常数吧),考虑到访问连续性,确实值得这么做。

  • upd:实测总是确实较快,但为了最好的效果,不可放在 dfs 内求,而应拉出来用循环,以 step 为层求。

重剖求 LCA

  • 参见“轻重链剖分”。
posted @ 2023-02-07 09:51  未欣  阅读(58)  评论(0)    收藏  举报