[算法学习] 虚树
概念
虚树,是对于一棵给定节点数\(n\)的树\(T\),构造一棵新的树\(T′\)使得节点总数最小且包含指定的某几个节点和它们的\(\text{LCA}\)
利用虚树,可以对于指定多组点集\(S\)的询问进行每组\(\Theta(|S|\log n+f(|S|))\)的回答,其中\(f(x)\)指的是对于树上x个点的情况下单组询问这个问题的时间复杂度.可以看到,这个复杂度基本上与n无关了.
这样,对于多组询问的回答就可以省去每次询问都遍历一整棵树的\(\Theta(n)\)复杂度了.
虚树的构建
首先最暴力的方法,就是枚举点集上的点,\(\Theta(|S|^2 \log n)\)的枚举集合上的点,求\(\text{LCA}\),然后加入集合之中。
那么我们考虑如何\(\Theta(|S| \log n)\)的去构建这棵树。
我们先对\(S\)集合中的点按照\(\text{dfn}\)排序。
我们维护一个栈,自栈底到栈顶的元素的\(\text{dfn}\)单调递增,即栈底到栈顶的元素表示根节点到\(u\)的一条路径。
现在分情况讨论
如果\(\text{stack}\)中的元素只有一个(只有根节点) 这种情况最简单,直接将这个节点加进去。
否则 继续分情况考虑 下文中默认栈顶元素为\(stk[top]\) 新加入的点为\(x\) \(LCA\)为\(lca\)
我们求\(lca = LCA(stk[top], x)\)(原树中)。
显然\(lca\)不可能为\(x\),因为若成立\(dfn[lca = x] <= dfn[x], dfn[stk[top]]\) 但是\(dfn[x] > dfn[lca]\) (加入的顺序) 矛盾。
所以\(lca\)要么等于\(stk[top]\),那么这种情况很简单,直接将\(x\)放入栈中。
否则,说明\(x\)和\(stk[top]\)不属于同一棵子树,同时也说明\(stk\)中的元素已经构建完毕了,
所以我们要不断弹出元素,在弹出元素的同时不断的连边,知道\(dfn[stk[top - 1]] < dfn[lca]\)或者\(top < 2\)为止.
然后若此时的\(stk[top]\)和\(lca\)相同,那么我们直接加这个元素,否则我们把\(lca\)和\(stk[top]\)连接起来,然后在将\(stk[top]\)弹出,\(lca\)和\(x\)压入。
汇总一下 代码如下
inline void insert(int x) {
if(top == 1) {stk[++top] = x; return;}
int lca = LCA(x, stk[top]);
if(lca == stk[top]) {stk[++top] = x; return;}
while(top > 1 && dfn[stk[top - 1]] >= dfn[lca]) {
Addedge(stk[top - 1], stk[top]);
top -- ;
}
if(stk[top] != lca) {
Addedge(lca, stk[top]);
stk[top] = lca;
}
stk[++top] = x;
}