省选算法学习-数据结构-虚树

这次要学的是一个听起来很虚的东西

没错写起来更虚

毕竟都是在虚的东西上面操作……

 

虚树,顾名思义,就是一棵不真实的树【大雾】

它可以对于一部分点保存整棵树的所有信息,而对一部分点选择忽略,这样可以增加dp/点分治的效率

 

为了给大家一个更好的例子(免得看不懂虚树到底能干嘛),我们先来看一个题目:消耗战

容易看出,这道题需要对每个询问做树形dp,然后每次在原树上dp都有$O\left(n\right)$的时间开销,m次询问做完肯定TLE

 

那么怎么解决呢?可能有些人会说离线瞎搞,但是万一这道题强制在线呢???

这时候就要用到虚树这个数据结构了

 

 

考虑一组询问,我们在树上有一些点是”询问点“,剩下的点不是

也就是说,其实我们只是需要对这些”询问点“处理信息,然后再把询问点的信息合并就好了

诶,等等,万一询问点之间不全都是父子关系怎么办?

那看来我们还要加上询问点们的LCA,也一起放到这课树里面

这样,我们的新树中就有这组询问的所有询问点,以及它们互相之间的LCA了,然后这个点的数量级是$O\left(询问中的点数\right)$级别的

我们看到,题目数据范围里说,所有的询问点数总和不超过300000

OK!这样dp就不会超时了!!

 

所以上面这一步中,我们找到了做题的思路:把虚树建出来,在虚树上dp

那么,怎么实现构建虚树的过程呢?

这个好像有点麻烦,因为我们并没有什么方法能对于一个点集合求出它们的附加LCA,所以我们得换个角度下手

这里的思路比较复杂,说出来也价值不大,所以就直接提供方法了

 

我们维护一个单调的栈,这个栈里的元素关于深度单调,也就是说我们栈里面保存一条从当前根开始的树链。

先把所有的”询问点“按照dfs序排个序,然后依次把它们加入栈中

每一次,我们设grand为当前待入栈节点和当前栈顶节点的lca

然后做这样一个循环:

如果当前的栈顶深度大于grand,我们就在栈顶和栈的第二个元素中间连一条虚树边,并把栈顶弹掉,直到栈顶的深度小于等于grand

此时让grand和上一个被弹出的元素连边

这时再把grand加入栈顶,然后再把待入栈节点加进来

所有的”询问点“都入栈了以后,再把栈里的元素一个一个弹掉并连边就好了

注意这个过程中,所有的加边只在弹栈过程中进行,注意不要写错

 

这样建出来的虚树就是比较点数少的了,而且在求dfs序的同时我们还可以维护深度和子树大小之类的信息,后面在虚树上依旧可以使用

不过,有的时候(例如上面的例题),某个节点一定不会作为询问点,此时可以先把这个节点(比如例题中的一号)加入栈,作为虚树的根

否则就需要在每一次新加入询问点的时候判断栈是否为空,如果是空的就要直接把这个节点入栈

这样的方法,最后栈里剩下的那个元素,就是虚树的根了

当然,有的题目因为需要一些和dfs时确定的根节点(比如1号)有关的信息,所以必须以一号作为根,这种时候就要在dp里面判断一下了

 

说了这么多,其实虚树的建法也大都和题目有关,因此还是要多看题

这里放三道虚树例题,分别对应上面那段讲的三种情况

SDOI2011消耗战

HEOI2014大工程

HNOI2014世界树

可以看到虚树一般作为优化dp时间复杂度的一种手段,题目真正的精髓还在dp上

 像世界树这道题就dp非常恶心,写起来贼难受......

 

总结一下,虚树就是一个优化树的结构的数据结构【好绕啊】,一般结合dp或者点分治使用(好像比较少见点分治的)

当然可能有什么结合树上莫队啊启发式算法啊之类的恶心题,但是见得不多就是了

实际上,虚树这个算法源于去除冗余信息的思维,它把多的、不需要的信息集成在了虚树边上

这一点在世界树那题里面特别体现了出来

posted @ 2018-03-18 14:10  dedicatus545  阅读(267)  评论(0编辑  收藏  举报