虚树,是一种针对树上点集的强力运算。它可以在\(O(k\log k)\)(其中\(k\)是点集大小)的时间内,建出一棵包含点集中所有节点,以及其中某些点的lca的树出来。这棵树就被称作虚树。之后就可以在虚树上进行操作了,例如树形DP等。

建出虚树的操作主要是这样的:

  1. 我们维护一个栈,从栈顶到栈底构成原树上一条深度递减的链。注意这条链不是完整的链——它只记录了链上的关键节点。

  2. 我们首先将原树的根节点加入虚树,方便维护。接着,将点集中所有点按照dfs序排序,然后依次加入虚树。

  3. 当一个点被加入虚树时,如果栈为空,直接将该点加入栈,并直接结束函数;否则,求出它与栈顶节点的LCA。

  4. 如果栈中第二个元素的深度小于LCA的深度,在虚树上连一条边\((\text{栈中第二个元素},\text{栈顶元素})\),并弹出栈顶节点。重复此操作,直到第二个元素的深度大于等于LCA的深度。

  5. 如果栈顶元素的深度小于LCA的深度,连一条边\((LCA,\text{栈顶元素})\),并弹出栈顶元素。

  6. 如果栈顶元素不同于LCA,将LCA入栈。

  7. 将(3)中那个点入栈,并结束该函数。

  8. 在所有东西全部加入虚树后,清空栈,在清空的过程中同时连边。

代码:

void ins(int x){
	if(!tp){stk[++tp]=x;return;}
	int lca=LCA(x,stk[tp]);
	while(tp>=2&&dep[lca]<dep[stk[tp-1]])v[stk[tp-1]].push_back(stk[tp]),tp--;
	if(tp&&dep[lca]<dep[stk[tp]])v[lca].push_back(stk[tp--]);
	if(!tp||stk[tp]!=lca)stk[++tp]=lca;
	stk[++tp]=x;
}

理解:

因为我们事先将节点按照dfs序排序,所以当新节点的深度大于栈顶节点深度的时候,就意味着原树中栈顶节点子树内所有节点都已经被加入虚树,可以弹出了。

然后,这个lca为了维护虚树的形态,也应该被加入虚树。

总复杂度\(O(k\log k)\),瓶颈在于按照dfs序排序。


一些定义:

虚树:即上文代码所建出的树

原树/实树:原本的树

实点:给出点集中的点

虚点:在建出虚树时为了维护形态而添加的点(即实点的LCA之类)

树外点:原树中不在虚树内的点

树点:虚树内的点(大部分没有歧义的时候,“节点”就特指树点)