虚树
虚树
虚树看起来很简单的样子。
事实上也的确很简单。
我们先来知道一下虚树是用来干什么的。
对于一个问题,我们知道他可以做树型\(dp\)
\(dp\)的类型大致是给你\(k\)个关键点,而\(dp\)的结果与这些关键点有关系
有\(m\)组询问,需要你对于每组询问进行回答。
并且有条件\(\sum k\)与\(n\)是同阶的。
如果每次对于所有点都做一遍\(dp\),复杂度达到了\(O(nm)\)
所以,我们需要把复杂度往\(\sum k\)上靠。
这样,就有了虚树。
我们仔细想想,每次\(dp\)的时候是否真的所有点都需要计算呢?
答案显然是不。我们只需要计算那些对于答案有影响的点。
再来思考一下哪些点对于答案有影响呢?
关键点显然是有影响的。
同时,\(dp\)的时候显然需要合并答案,因此,关键点之间的\(LCA\)也是有影响的
所以,我们构建的虚树包括两种点:关键点,\(LCA\),还需要一个毫无关系的节点作为根节点来合并最后的答案。
因此,构建虚树就相当于把这些点拿出来,然后按照原树中的方式连接好就行了。
怎么做呢?
先考虑怎么求出\(LCA\),显然不可能\(O(k^2)\)去求
事实上,我们只需要把关键点按照\(dfs\)序(\(dfn\))排序,然后把相邻点的\(LCA\)求出来就好了
这样子,我们最多可能拿到\(2k\)个点(所以注意一下数组的大小,是\(2\)倍)
现在考虑怎么构建。
把现在所有的点按照\(dfn\)排序,维护一个栈,栈中的点全部在同一条链上,节点的深度从栈顶到栈底递减。
对于新加入的一个节点,检查新的节点是否在栈顶节点的子树中,
如果在,直接加入栈中,仍然满足链的性质,并且栈顶就是当前点在虚树上的父亲。
证明?因为已经按照\(dfn\)排序,因此一条链肯定从上至下依次出现。
如果当前点不在栈顶节点的子树中,证明当前栈顶节点的子树中已经没有虚树中的点了
直接把它弹出来,继续检查即可。
这样我们就构建出了虚树了。
大致的代码如下:
sort(&p[1],&p[K+1],cmp);
for(int i=K;i>1;--i)p[++K]=LCA(p[i],p[i-1]);p[++K]=1;
sort(&p[1],&p[K+1],cmp);K=unique(&p[1],&p[K+1])-p-1;
for(int i=1,top=0;i<=K;++i)
{
while(top&&low[S[top]]<dfn[p[i]])--top;
Add(S[top],p[i],0);S[++top]=p[i];
}
至于虚树要怎么用???
那就因题而异了。