虚树-学习笔记
简介
虚树,是一种处理树上动态规划的数据结构。但他适用于这种情况:多组询问,每次询问一个点集,求这些点集的答案。我们当然可以每次都对整一棵树跑一遍,但如果有 \(1e5\) 个询问呢?这时候别的算法很难处理,于是虚树就诞生了。
如果题目没有多组询问的时候可以用树上 DP 做,那么就可以考虑虚树了。
算法
我们设每次询问的点集为关键点,点击大小为 \(k\)。
虚树处理点集的方法比较特殊。对于一道题目,如果我们发现每组询问只会涉及到询问的点集和他们两两之间的 LCA,那么就可以用虚树来做。
对于每组询问,我们可以在线处理。我们相当于只是把所有需要用到的点处理出来,然后按照原树的祖先后代关系重新建一棵虚树,然后在虚树上跑 DP 就可以了。
建树的话,我们需要每个点,以及他们两两之间的LCA。可以证明,每次总的点数不会超过 \(2k\) 。因此,虚树算法的时空复杂度是 \(\text{O}(\sum k)\),满足我们的需求。
有一个经典的建虚树方法,可以做到 \(\text O(k)\) 复杂度。就是先把所有关键点按照他们原树上的 dfn 序排序,然后一个点一个点插入。具体的话,用单调栈维护当前虚树上的一条链,然后插入一个点和他与链上所有点的 LCA。具体的话就 懒得写了,就是分类讨论一下,一共有四种情况,分别是:
- 这个点是当前这条链的低端。那么直接插入链尾。
- 这个点与原链尾的 LCA 是倒数第二个点。此时弹出栈顶,插入这个点。
- 这个点与原链尾的 LCA 在倒数第二个点下方。此时弹出栈顶,插入 LCA,插入当前点。
- 这个点与原链尾的 LCA 在倒数第二个点上方。此时弹出栈顶,并循环,直到不是当前情况。
这里可以这么做是因为当前点和链上的点的 LCA 最多只会新建一个点。当然这里只是理清一下自己的思路,如果看不懂可以去看别的博客。
然后建完树跑 DP 就好了。当然有些题难点在于虚树而不在 DP,还是要视实际来取舍。
注意点:
- 每次清空虚树只需要把当前树上的边清空就好了。记得清空不是关键点但是是 LCA 的点!
- 每次 DP 记得初始化数组。
- 为了方便,我们一般都是先把 \(1\) 号点插入虚树。这样可以避免讨论一些边界情况而不会影响答案。当然还是要具体题目具体讨论。
那么虚树就说完了。
例题
比较模板了。注意建出虚树后两点之间的权值要用倍增或者树剖求出原树上两点之间边权最小值。树剖的话就是先是跳 top,然后在同一条链比较麻烦,但应该也是可以做的。就是对于每一条链的链底开一个 vector,保存这个点往上跳 k 步的最小值,这样复杂度也是对的。但我没打过 因为要用 vector,可能常数略大。
大码量题。主要是 DP 的细节。需要保存每个点为根的子树中,每个关键点到自己的距离和,距离最大值,距离最小值,以及每个儿子为子树的距离最大值的最大值和次大值,最小值同理。但也算比较套路了,做题多了就可以自己推出来。
算是比较思维吧。也是 DP 比较不好想,需要一番思考。
这两道题我自己也没做,留个坑以后来填。
那么虚树这一章就完满结束了。