虚树

虚树

虚树看起来很简单的样子。

事实上也的确很简单。

我们先来知道一下虚树是用来干什么的。

对于一个问题,我们知道他可以做树型\(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];
}

至于虚树要怎么用???

那就因题而异了。

posted @ 2018-05-21 11:36 小蒟蒻yyb 阅读(...) 评论(...) 编辑 收藏