虚树 学习笔记

虚树是什么

对于一棵树,如果我们多次查询,第 \(i\) 次查询,给出树上的 \(k_i\) 个节点,查询它们公共的信息、互相的贡献等等。每次 dfs 整棵树,时间复杂度是 \(O(nT)\) 的。但是如果我们每次查询把无用的节点删除,只保留 \(k_i\) 个节点以及他们的 lca,那么最多会有约 \(2k_i\) 个节点,再进行 dfs,总共时间复杂度是 \(O(n\sum\limits_i k_i)\) 的。

如何建立虚树

我们先把 \(k\)​ 个关键节点按照 dfs 序 排序,维护当前的最右链(带着炸毛环境的画外音响起:维护右链!)。我们用一个栈维护当前的右链是什么,一开始栈顶元素就是根节点。(节点上的数字代表它在栈中下标)

image-20210830100220414

如果我们加入一个新节点,新节点在原树上与栈顶元素的 LCA 如图:

image-20210830104516632

那么我们就不断将栈顶和栈第二连边(比如当前栈顶是 4,栈第二是 3,那就连边 4-3,然后弹栈)。弹栈表示这个点已经不是最右链,将它放入虚树中并弹出。

不停地弹栈,直到栈中元素只有一个(此时再弹就空了),或者栈顶元素比 LCA 浅,我们就停止弹栈。如果 LCA 刚好不是栈顶元素,那么就连边 top-LCA ,然后将 LCA 加入栈顶。最后将当前的元素加入栈顶。

到最后,如果栈中还有元素,那么就不断弹栈,将栈顶与栈第二连边。

虚树就建好啦!

void Vtree(){
	sort(b+1,b+m+1,cmp);
	top=1,sak[top]=1;
	rep(i,1,m){
		if(b[i]==1)continue;
		if(top==1){
			top++,sak[top]=b[i];
			continue;
		}
		int lca=Dlca(b[i],sak[top]);
		while(top>1&&dep[lca]<=dep[ sak[top-1] ]){
			edge(sak[top],sak[top-1],fst,e);
			edge(sak[top-1],sak[top],fst,e);
			top--;
		}
		if(lca^sak[top]){
			edge(sak[top],lca,fst,e);
			edge(lca,sak[top],fst,e);
			sak[top]=lca;
		}
		top++,sak[top]=b[i];
	}
	while(top>1){
		edge(sak[top],sak[top-1],fst,e);
		edge(sak[top-1],sak[top],fst,e);
		top--;
	}
}

虚树例题

SvT

建出后缀树后,就变成了求每个后缀对应的节点的两两 LCA 深度之和。

用虚树就可以低时间复杂度树形 DP 了。

posted @ 2021-08-30 12:08  BlankAo  阅读(35)  评论(0编辑  收藏  举报