虚树
对于一株树,如果我们需要在一株n个结点组成的树上做树上DP,但是其中仅有m个结点需要特殊处理。显然我们可以在原树上做一次完整的树上DP,其时间复杂度为O(n)。但是有的时候m远小于n,且这种请求可能不止发生一次,那么我们将无法在给定时间内完成任务。
实际上每次DP真正涉及到的结点数并不多,其仅为m个结点以及m个结点中任意两个结点的lca。所以如果我们能借助这些m个结点和结点对所指出的lca按照树中的祖先后代关系重组一株新树,树的大小将大幅缩小,这样就能完成任务了,而新建立的树称为虚树。
说一下流程:
1.首先我们对树进行先序遍历,并按照结点访问的顺序记录结点属性dfn,dfn越小表示其在先序遍历时越早被访问。这部分时间复杂度为O(n)。
2.之后我们利用st或树上倍增算法对树进行预处理,这部分时间复杂度为O(nlog2n)。
3.之后我们处理所有的请求,对每个请求建立虚树,并在虚树上做DP操作。
建立虚树的流程如下:
1.选取所有特殊结点,并加入到列表中。
2.对列表按照dfn属性进行从小到大排序。
3.将每个列表中的特殊结点与与其左右相邻的特殊结点之间的lca加入到列表中。
4.重新按照dfn属性排序列表。
5.建立一个栈,将列表中dfn属性最小(下标最小)的结点加入到栈中。
6.之后从前到后遍历列表中的剩余元素。弹出栈尾所有非当前遍历元素祖先的结点,并将当前遍历元素的虚树父亲设为栈尾元素,之后将当前遍历元素加入到栈尾。重复这个过程。
先说明建立虚树的步骤3会将任意两个特殊结点的lca加入到虚树中。考虑a,b,c为三个前后相邻的特殊结点。很显然lca(a,b)与lca(b,c)有祖先后代关系,因为二者均为b的祖先,且lca(a,c)=lca(lca(a,b),lca(b,c))。故利用传递性我们能保证任意两个特殊结点的lca都被加入到了列表中。
而第5步,我们建立栈并将dfn属性最小的结点加入到栈中,若栈尾元素不是当前遍历元素的祖先结点,而当前遍历元素一定处于栈中,则表示栈尾元素所代表的子树已经建立完毕,我们将其弹出则表示不会有新的结点追加到其后。(这都是dfn的性质)
很显然对于m个特殊结点,我们仅仅选出了拥有2m-1个结点,即我们建立的树相对于问题的规模是线性的。