日常学习——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为它的直系儿子,1号点的dp值即为最终答案。
后续
提供一个建虚树的板子
这里还有些用到虚树的题,可供参考。
Bzoj 3991 SDOI2015寻宝游戏
Bzoj 3611 Heoi2014大工程
Bzoj 3572 Hnoi2014世界树
Luogu_4242 树上的毒瘤
Bzoj 5287 Hnoi2018毒瘤
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步