zoukankan      html  css  js  c++  java
  • 省选算法学习-数据结构-虚树

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

    没错写起来更虚

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    然后做这样一个循环:

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

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

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

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

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

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

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

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

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

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

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

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

    SDOI2011消耗战

    HEOI2014大工程

    HNOI2014世界树

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

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

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

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

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

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

  • 相关阅读:
    poj3348 Cow
    poj3348 Cow
    日常。。。强行续
    日常。。。又又续
    日常。。。又又续
    日常。。。又续
    内存检索
    MyLayer MyScene
    冒泡排序
    Array数组的排序与二分查字法
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/8595330.html
Copyright © 2011-2022 走看看