虚树学习笔记

虚树一般解决树上询问一些节点的问题。

通过一个例题来认识:

P2495

有一棵 \(n\) 个节点的树,边有边权,断掉一条边的代价就是边权,有 \(Q\) 次询问,每次给出 \(k_i\) 个重要点,每次求出通过断边使得任意一个重要点都与 \(1\) 号节点不连通的最小代价。
\(n\le 2.5\times 10^5,\sum k_i\le 5\times 10^5\)

经典题。

先考虑 \(Q=1\) 的情况,显然是一个树形 dp,设 \(f_{i}\) 表示 \(i\) 子树中通过断边使得 \(i\) 所在联通块中没有重要点的最小代价,容易得到状态转移方程:

\[f_{x}=\sum_{y\in son} \min(w_i,f_{y})\\ \]

其中 \(w_i\) 表示边权,如果 \(x\) 是关键点,\(f_x\) 为正无穷。

然后考虑多次询问咋做。

先考虑一条链的情况,比如下图:

LKPCIf.png

如果 \(x,y\) 中间都不是重要点,那么实际上断开的代价就是 \(x\)\(y\) 路径上边权最小值。

如果 \(x\) 不是重要点,那么就可以直接由 \(y\) 转移。

推广到树上,我们发现有很多点可以不用 dp。

具体来说,只需要把重要点和它们的 LCA,拿出来,根据原树上的关系进行连边,边权就是链上边权最小值,这样建出的树的 dp 答案与原树相同。

证明较简单,因为任意两点间都是没有重要点的链,所以上面的点可以删去。

这样建出的树就是虚树,可以通过下面建虚树的算法可知,虚树的点数是 \(O(k_i)\) 级别的,可以通过。

然后考虑咋建出虚树。

先把点按 dfs 序排序,从小到大加入,维护一个栈,开始时,先把 \(1\) 进栈,栈维护一条链。

如果当前点在栈顶的子树内,直接连边即可,然后入栈。

否则考虑如下图:

LKnY9I.png

黑色是栈中的点,红色是新加入的点。

所以找到第一个是红色节点祖先的黑色节点,然后把它下一个点与红色节点的 LCA 求出,直接连边即可。

注意特判 LCA 在栈中的情况。

然后把下面的点全部弹出,红色节点和 LCA 入栈,形成新的链。

一直做下去即可,最后得到一棵树,边权可以直接倍增做,直接 dp 即可,时间复杂度 \(O(n\log n)\)

code


posted @ 2022-04-13 13:06  houzhiyuan  阅读(50)  评论(0编辑  收藏  举报