虚树学习
口胡一下怕忘了。应该会有错留坑回来改。
哔哔一下题意:边有边权。$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]);