日常学习——Virtual_Tree(虚树)

概念

虚树,顾名思义就是虚构的树,它是一种用来解决树上问题的算法,主要思想是只将原树上必要的点和它们的最近公共祖先取出来,构成一棵虚树,并保留他们在树上的相对关系。

引入

我们先来看一道题:
给定一棵n个点的树,每次询问给定一个大小为k的点集,你需要切掉一些边,使得点集中的点均不与1号点联通,而每条边都有被切掉所需的代价,你还要让总代价最小。\(2 \leqslant n \leqslant 250000,,\sum ki \leqslant 500000,1 \leqslant k_i \leqslant n-1\)
(来源:Bzoj_2286 Sdoi2011消耗战)
对于单个询问,我们可以用Tree_Dp在O(n)的时间内得到答案,这样显然太慢。但是,我们发现实际上询问的总点集大小并不大,如果每次询问能不将整棵树都遍历而只遍历询问的点,那么复杂度就可以降下来了。因此我们需要用到虚树。

构建

构建虚树采取的思路是:将点“从左到右”地添加。
我们先要将树遍历一遍给每个点记一个dfs序,然后将询问点按dfs序排序。
用栈维护一条当前虚树内“最靠右”的一条链,按顺序加点。
假设当前准备加入的点为x,如果当前栈顶不是x的祖先,则弹栈,并向上一个弹出的点(仅在本轮操作中)连边,直到是或栈为空为止,顺便记录弹出的点与点x的最近公共祖先LCA(弹出的所有点与点x的最近公共祖先一定相同)。
将LCA与最后一个弹栈的点连边,之后再判断LCA是否已在栈顶,否则入栈。点x入栈。这样栈中一定是“最靠右”的链。
复杂度\(O(klogn)\)
以下图为例,绿色点表示询问点,蓝色点表示进入虚树的点,红色点表示栈里的点。

加入2号点:

加入4号点:

加入6号点,2和4连边:

加入9号点,2和6连边、1和2连边:

最后再将栈内的1和9连边。

解决

那么我们回到原题,现在知道如何建虚树了,如果解决问题?
首先我们要有一个小技巧,就是对于一个询问点,如果某个询问点是它的祖先,那么就可以把它从询问点中删除。这样询问点之间就没有祖先关系了。我们还需要为每个点记一个值,为它到根(1号点)的路径中最小代价的边,记为\(v_x\)
那么建出虚树后,我们对于虚树中的叶子节点x,其dp值为\(v_x\)。对于非叶子节点x,dp值为\(min(v_x,\sum dp_{son})​\),son为它的直系儿子。

后续

这里还有些用到虚树的题,可供参考。
Bzoj 3991 SDOI2015寻宝游戏
Bzoj 3611 Heoi2014大工程
Bzoj 3572 Hnoi2014世界树
Luogu_4242 树上的毒瘤
Bzoj 5287 Hnoi2018毒瘤

猜你喜欢

转载自www.cnblogs.com/Alseo_Roplyer/p/10367231.html