虚树

说在前面

有一类问题,整棵树的节点数非常多,但是询问只和其中的若干个关键节点(以及必要的维护他们之间的连通性的节点)有关,这类问题我们可以采用虚树来优化。

构建

一般采用单调栈构建虚树,首先将所有关键节点按 \(dfn\) 排序,把 \(1\) 号节点入栈。
单调栈中维护的一直是一条链,我们扫一遍所有的节点,判断这个节点和栈顶的 LCA 是不是栈顶,如果不是就进入判断阶段,否则说明二者在一条链上。

判断次大节点的 dfn 是否比 LCA 大,如果是,说明次大节点的深度大(栈顶更大),弹出栈顶并与次大节点连边。
判断一下 LCA 是否等于当前的次大节点,如果不是则弹出栈顶并连与 LCA 连边,把 LCA 入栈。
如果不是说明 LCA 已经是次大节点了,直接弹出栈顶并与 LCA 连边即可。
最后把当前节点入栈,继续遍历。

code:

点击查看代码
void build(int n){
	sort(imp + 1, imp + n + 1, cmp);
	memset(head, 0, sizeof(head));
	cnt = 0;
	s[top = 1] = 1;
	for(int i = 1; i <= n; i++){
		if(imp[i] == 1) continue;
		int lc = lca(s[top], imp[i]);
		if(lc != s[top]){
			while(dfn[s[top-1]] > dfn[lc])
				add(s[top], s[top-1]), top--;
			if(lc != s[top-1])
				add(s[top], lc), s[top] = lc;
			else add(lc, s[top]), top--;
		}
		s[++top] = imp[i];
	}
	for(int i = 1; i < top; i++)
		add(s[i], s[i+1]);
}

例题

P2495 [SDOI2011] 消耗战

朴素的树形 DP 很好想,要么是切断当前节点,要么是切断所有儿子,维护一下到根节点的路径上的最小值,转移即可。

但是每次都做一遍复杂度是 \(O(nm)\),非常不乐观,所以考虑构建虚树后在虚树上DP即可。
注意到虚树的构建过程天然适合我们清空树,每次把节点入栈前清空即可(如果memset/fill会收获TLE的好成绩)。

P4103 [HEOI2014] 大工程

同样的建立虚树,然后考虑 DP,问题 1 直接转成每条虚树上的边(对应原树上的一条路径,长度即为虚树上两点的深度差)被多少路径经过,dfs 统计之,问题 2、3 类似树的直径,统计到子树中的关键点最小/最大长度,直接求即可。

练习题

P3233 [HNOI2014] 世界树
#6184. 无心行挽
P4220 [WC2018] 通道

不言而喻

图片名称
posted @ 2023-07-11 22:52  Kun_9  阅读(16)  评论(0编辑  收藏  举报