LCA 与树上差分

Posted 未欣的 blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

  • 参见“轻重链剖分”。

以上是关于LCA 与树上差分的主要内容,如果未能解决你的问题,请参考以下文章

[LCA 树上差分 点差分 板子] Max Flow P

[LCA 树上差分 点差分] 松鼠的新家

[LCA 树上差分边差分] 闇の連鎖

[填坑]树上差分 例题:[JLOI2014]松鼠的新家(LCA)

松鼠的新家 (lca+树上差分)或(树链剖分)

习题:电压(LCA&树上差分)