虚树学习

口胡一下怕忘了。应该会有错留坑回来改。

例题:bzoj2286: [Sdoi2011]消耗战 

哔哔一下题意:边有边权。$m$次询问,每次给定$k$个点,割掉若干条边使得$1$号点与给定的点不连通,要求代价最小。

$n leq 2e5 , sum k leq 5e5$

首先这个$sum k$就很特别。是建虚树的契机。

我们不可能每次跑一遍$O(n)$的树$dp$,那我们能不能每次$O(k)$呢?

做法就是每回只在关键点上做$dp$,就要求我们建出只有关键点的树。即虚树。注意关键点不仅是给定的$k$个点,还有它们所有点两两的LCA。

实际上算上LCA也只有不超过$2k$个点。想一想为什么。(没事,我自己想想而已)

靠全是废话。直接说咋构建。

所有点按dfs序排序。按顺序插入栈中。设栈顶元素为$p$,现在要插入$x$。

栈里维护的是根到$p$的一条链。每插入一个点,和栈顶只有两种关系:

$p$和$x$的LCA是$p$;是另一个点(可能在栈里可能不在,可能是给定点可能不是)。根据dfs序,不存在第三种情况。

第一种情况,直接进栈,注意不用加边。因为可能会出现和之后点的LCA在$p$之下,这种情况下应该是$p$连这个LCA,LCA再连$x$。而这时候这个LCA还不知道是谁甚至有没有。

另一种情况,我们令$LCA(p,x)$为$fa$。这时说明$p$的子树已经遍历完了,不然不会轮到$x$。我们前面说过这个$fa$可在可不在,但一定在栈当前维护的链上。(真tm废话)于是开始弹栈。

我们称栈顶的第二个元素为$q$,如果$q$的深度(这里用dfs序也是一样的道理)大于$fa$,$fa$在$q$之上,$p$与$q$连边,弹出$q$。直到$q$的深度等于或者小于$fa$的,说明$q$就是$fa$或者$fa$在$p,q$之间,$fa,p$连边,$fa$入栈,$p$弹栈(要是$fa=q$就不用了),$x$入栈。

所有点对的LCA我们可以$O(logn)$时间求出,所以所有询问构建虚树的总复杂度就是$O(sum k logn)$的。听着高大上的东西,其实道理很简单。给我的感觉就是和缩点的$tarjan$算法一样,只是一个工具,难点还是在于$dp$吧。

板子等我熟练了在贴吧。。


upd

可能就是这样吧 

s[++top]=1;
for(int i=(a[1]==1)+1;i<=k;i++){
    int f=lca(a[i],s[top]);
    if(f==s[top]){
        s[++top]=a[i];continue;
    }
    while(1){
        if(dep[s[top-1]]<=dep[f]){
            add(f,s[top]);
            top--;
            if(s[top]^f)s[++top]=f;
            break;
        }
        add(s[top],s[top-1]);
        top--;
    }
    s[++top]=a[i];
}
while(--top)add(s[top],s[top+1]);
原文地址:https://www.cnblogs.com/orzzz/p/8232344.html