虚树学习笔记
又是一个全机房只有我不会的知识点
同样,这篇文章也不适用于对这个知识点一无所知,且需要大量严谨证明的同学阅读
定义
虚树是一个用来优化树形dp的数据结构
考虑将原树中的某一个点集 提取出来,构成一棵新的并能保持原树结构的一棵树。
为了保持结构,我们除了集合的点之外,还需要在虚树中添加中任意两点的。
因为有这么一条性质:
一个点集中两两的集合,就是把按
dfn序
排序后,相邻两点的的集合这个性质很好理解
于是我们发现,总点数是级别的
构建
考虑求集合的虚树
设关键点集合表示集合中两两点的,以及 本身。用上面的那条性质就能轻松求出集合
我们把集合按照dfn序
排序
接下来,我们使用一个栈维护一条从根下来的关键点链,然后不断对于这个栈进行操作,每次将新加进来的点与栈顶元素连一条边。
考虑从一条关键点链跳到另外一条关键点链上的过程,我们只需要不断弹栈,直到栈顶元素是当前新加进来点的祖先为止
感性理解就是有一条链从左往右不断移动,每个点只需要连上它在这条链的父亲
核心代码
1 | sort(A + 1, A + K + 1, cmp_by_dfn); |
应用
虚树往往用来处理一些答案与关键点有关,且关键点总数不是很大的树形dp题
每次构建虚树,在虚树上进行dp即可,复杂度是的
具体来说,虚树上的一条边就相当于原树中的某一条链,但是这一条链上的dp转移与关键点无关
于是我们就可以预处理出原树中链上与关键点无关的转移(或者转移系数之类的),每次dp的时候只需要考虑当前虚树上的转移,虚树边上的转移直接利用预处理的信息即可
到了这里,我们就可以理解虚树的本质了
在与关键点有关的树形dp中,有一些边上的转移实际上是与关键点无关的,而如果我们对于每一次询问都进行dp的话,显然会有很多重复转移。于是我们可以预处理出这些重复的转移
这样便产生了虚树,它的本质就是把与当前关键点有关的转移单独提出来,单独进行转移,而虚树边上的转移都是与当前关键点无关,可以预处理出来的
因此虚树的思想还是对在暴力计算中冗余、重复处理的东西进行优化,从而优化时间复杂度