全机房的小伙伴们都早就会虚树了,就我不会,于是决定学习一下虚树。前两天看了一些别人的讲解,大体明白了虚树的思想,于是写一篇学习笔记。
虚树经常用来解决在一棵树上选出若干点,有若干次询问,每次询问求这若干点的某一个东西,一般选出的点的总数是1e5量级的。这类题的一个暴力做法通常是可以对于每次询问都暴力树形dp,但是这样复杂度就比较高,我们发现每次只选出了一些点,但是却对整棵树重新做了一遍dp,看起来很浪费,我们想要用尽可能少的点数构成一棵新的树,要求新的树能包含这次询问需要的所有信息,并且这棵树的点数尽可能少,这样我们只需要在这棵新的树上dp就能求出这次询问的答案了。我们把这棵新树叫做虚树。下面我们来讲一下对于一次询问,如何建出一棵对应的虚树。
首先我们对原树dfs,预处理出一个倍增LCA的父节点数组、深度和每个点的dfs序。然后对于每次询问,我们把所有点按照dfs序排序。我们一般选一个不会被选到的点作为虚树的根,如果都可能被选到,我的一个做法是选一号点为根,并且在询问时特判一下是否选中了一号点。update:还yy了一种写法,就是先求所有点的LCA,然后选这个点当根,显然这个点肯定在虚树里,并且可以看作是虚树的根。
我们构建虚树的方法是这样的:首先先把你选的根确定,我一般都是用1号节点,然后我们用一个栈来维护已经用到的一些点,栈中的点要满足是根到某个点形成的一条链上的点。当栈中的元素大于等于两个的时候,我们开始判断要对栈进行什么操作。我们按照dfs序插入所有当前询问选中的点。首先,我们求出当前要插入的点与栈顶元素的lca,接下来分类讨论。如果这个lca就是栈顶元素,那么意味着要插入的这个点是栈顶元素子树内的一个节点,他们相互连接之后仍然是一条链。那么根据题目需要,有时候我们需要插入当前点,有的时候可能在虚树上只需要保留lca。然后对栈顶元素不是两者的lca的情况进行讨论。如果栈顶的第二个元素的的dfs序大于等于lca的dfs序,就意味着当前点和栈顶元素不在栈顶第二个元素的同一棵子树内,那么我们就把栈顶的两个元素连边,然后删除当前栈顶,直到不满足上述条件。另外需要特判一下删完之后当前栈顶是否是lca,如果是的话就不用管,如果不是的话就把栈顶与lca连边,然后把原栈顶元素出栈,把lca入栈。在进行完上述操作后将当前要插入的点入站即可。最后如果栈不是空的,那么我们需要不断地把栈顶第一个元素和栈顶第二个元素连边,直到把栈弹空。
使用虚树的话要注意经常需要用到vector来存新树的边,否则的话你每次询问清空原来的连边信息时会复杂度退化。
学习笔记就讲到这里,例题如果需要的话请到博客里找吧。博主比较懒,就不挂链接了。