概念
虚树可以用来解决在树上的多次询问,约束总询问点数的动态规划问题,相较直接BFS的高复杂度,虚树可以将开销降到与单次询问点数相关的时间复杂度之内。
常见的套路是有多次询问,每次询问的时间复杂度都可以接受,但乘上询问个数就十分恐怖,然而每次询问需要用到的点不多(一般每次询问的点数和有一个与点数同数量级的范围),这时候可以考虑每次建一棵虚树。
建树
考虑动态规划合并多个询问节点,只有在遍历到关键的 $lca$ 时才会计算贡献,且可以直接以 $lca$ 代替以下节点,所以可以设计出以下算法流程:
- 维护一个以 $\text{dfn}$ 序为关键字的单调栈,栈顶元素 $t_1$ 为所有已经处理过的节点中 $\text{dfn}$ 最大的节点,一般以根为初始节点,将所有询问节点按 $\text{dfn}$ 排序进行插入。
- 根据以上算法,一定有 $\text{dfn}[u]>\text{dfn}[t_1]$ 。记栈顶第二个元素为 $t_2$根据, $lca$ 与 $t_2$ 的关系进行分类讨论:
- 当 $lca=t_2$ 时,将 $u$ 入栈。
- 否则 $t_1$ 与 $t_2$ 分别在 lca 的两个子树中,则需要重复以下操作,直到 $p$ 到 $lca$ 中所有节点处理完毕,然后再将 $u$ 入栈。
- 当 $\text{dfn}[t_2]>\text{dfn}[lca]$ 时,连边 $t_2\to t_1$, 弹出 $t_1$。
- 当 $\text{dfn}[t_2]=\text{dfn}[lca]$ 时,连边 $lca\to t_1$. 此时 $t_1$ 到 $lca$ 已经处理完毕,即可跳出循环。
- 当 $\text{dfn}[t_2]<\text{dfn}[lca]$ 时,此时 $lca$ 在 $t_2\to t_1$上,建边 $lca\to t_1$,以 $lca$ 代替pp在栈中的位置。此时 $t_1$ 到 $lca$ 已经处理完毕,即可跳出循环。
##模板
1 | int seq[MAXN],scnt; |
注意每次建完虚树后都要清空,但不能用 memset
或 std::fill
之类无脑清导致复杂度爆炸,用多少清多少。
此写法为简化代码需要在 addEdge
函数中判 $u=v$ 的情况。
给定一棵 $n$ 个节点的树,每次
1 |
|
1 |
|