虚树学习笔记
关于虚树
对于一些在树上进行某些询问的查询,且每个询问实际用到的点并不多的时候,可以考虑建虚树来查询。
虚树的建立复杂度是 \(O(m \log n)\) 的,\(m\) 是虚树节点数量,\(n\) 是原树节点数量。也有方法可以做到 \(O(m \log m)\)。
例题
题目链接。
分析
注意到范围:\(\sum k_i \le 5 \times 10^5\),所以考虑虚树。
对于暴力做法,定义状态函数 \(f_i\) 表示使 \(i\) 不和其为根的子树中任意一个关键点联通的最小代价。不难有转移方程:
可以发现,我们实际上有用的点只有所有的关键点,任意两个关键点的 \(\operatorname{LCA}\) 和节点 \(1\)。如果我们建立一棵只有这些点的树,那么上面的暴力就可以用复杂度为 \(O(m)\) 的暴力实现。由于 \(m \le 2 \times k\),显然是可以过的。
建立虚树
这里介绍二次排序的做法。
先将所有的关键点按照 DFS 序排序。那么任意两个关键点的 \(\operatorname{LCA}\) 就相当于排序后相邻的两个关键点的 \(\operatorname{LCA}\)。
证明:
对于点 \(x\),若某两个关键点 \(A\) 和 \(B\) 的 \(\operatorname{LCA}\),那么 \(A,B\) 两点只会存在于 \(x\) 不同的两个儿子为根的子树中。很显然,在按照 DFS 序排序后,一定存在 \(i,i+1\),满足 \(a_i\) 和 \(a_{i+1}\) 是在 \(x\) 不同的两个儿子为根的子树中。
将得到的点再次排序去重后,就得到了虚树的所有节点。现在就只需要根据原树上节点祖孙关系给它们建边了。枚举相邻两个节点,然后将 \(a_i\) 成为 \(\operatorname{LCA}(a_{i-1},a_i)\) 在虚树上的儿子,虚树就建完了。
证明:
如果 \(x\) 是 \(y\) 的祖先,那么 \(x\) 直接到 \(y\) 连边。因为 dfn 序保证了 \(x\) 和 \(y\) 的 DFS 序是相邻的,所以 \(x\) 到 \(y\) 的路径上面没有关键点。
如果 \(x\) 不是 \(y\) 的祖先,那么就把 \(\operatorname{LCA}(x,y)\) 当作 \(y\) 的的祖先,根据上一种情况也可以证明 \(\operatorname{LCA}(x,y)\) 到 \(y\) 点的路径上不会有关键点。
所以连接 \(\operatorname{LCA}\) 和 \(y\),不会遗漏,也不会重复。
复杂度 \(O(m \log n)\),最后跑一遍暴力就行了。
练习题
CF613D
对于 DP,定义状态函数 \(f_i\) 表示使以 \(i\) 为根的子树中任意两个关键节点都不联通的最小代价,\(g_i\) 表示以 \(i\) 为根的子树中是否剩下 \(1\) 个与 \(i\) 联通的点关键点。则有转移方程:
答案为 \(f_1\)。对于无解的情况,就是相邻两个节点都是关键点,这个在读入的时候判断即可。
世界树
分情况讨论。
定义 \(g_i\) 表示离 \(i\) 最近的关键点的编号。对于虚树上 \(u,v\) 两个点,若 \(g_u=g_v\),则 \(u \to v\) 中间的所有不在虚树上的点都会被 \(g_u\) 管理;若 \(g_u \ne g_v\),则它们一边一半。
第一种情况直接加即可。第二种情况,用倍增求出分界点 \(k\),使得 \(u \to k\) 中间不在虚树上点的数量不小于 \(son_k \to v\) 中间不在虚树上点的数量。
求 \(g_i\) 跑两遍 DFS 就行,难点在于求某条边外不在虚树上点的数量。可以自己推。
大工程
问题 \(1\)
定义 \(cnt_i\) 表示 \(i\) 为根子树中关键点的数量(不包含 \(i\)),\(sum_i\) 表示 \(i\) 为根子树所有关键点到 \(i\) 的距离。
则从 \(i\) 的 \(x\) 子树中选一个关键点,从 \(i\) 的另一个子树中选一个关键点的路径长度和就是 \((cnt_i - (cnt_x+1) ) \times (w_{i,x} \times (cnt_j+1) +sum_j)\)。
从 \(i\) 的 \(x\) 子树中选一个关键点到 \(i\)(\(i\) 是关键点)的路径长度和为 \(sum_i\)。
\(sum_i =\sum sum_x +w_{i,x} \times (cnt_x+1)\)。
问题 \(2,3\)
存下 \(i\) 为根子树中关键点到 \(i\) 的最短和最长路。同问题 \(1\) 的 \(2\) 种情况分讨即可。
Railway
考虑差分。
定义 \(cnt_i\) 表示 \(i\) 为根子树中关键点的数量。若 \(cnt_v < s \land cnt_v \ne 1\),则 \(u \to v\) 这条路径一定是需要删掉的,因为一定有别的关键点会经过 \(u\) 到达 \(v\) 为根子树中的某个关键点。
最后再跑一遍 DFS 将所有被选择次数不小于 \(k\) 的输出即可。
树上的毒瘤
为什么蓝+紫+紫+大码量不评黑。
对于虚树建出来之后 \(u,v\) 两个点之间的边权(\(v\) 是 \(u\) 的儿子),显然是 \(u\) 到 \(v\) 的颜色段数量。但对于 \(v' \to u \to v\) 这条路径,其颜色段数量一定不等于 \(v' \to u\) 和 \(u \to v\) 这两条边的边权和,而等于其和 \(-1\)。所以将虚树上两点的边权定义为颜色段数量 \(-1\),路径颜色段数量就等于边权和 \(+1\) 了。因为存在路径查询与路径修改,所以用染色的方式维护。
然后就是一个询问从关键点 \(i\) 出发,到达另一个关键点的路径长度之和的问题了。这就是一个点分治模板,对于枚举到的重心,将其子树的答案暴力更新即可。
复杂度是 \(O((\sum k_i)\log^2 n)\) 的,但是有点大常数。
Tree
定义状态函数 \(f_{i,j}\) 表示将以 \(i\) 为根子树中所有关键点分成 \(j\) 组的方案数。
若 \(u\) 不是关键点,则:\(f_{u,x} =\prod f_{v,x}\),因为 \(u\) 的儿子的子树中任意一个关键点都不会在 \(r\) 为树根的时候是 \(u\) 另一个儿子的子树中任意一个点的祖先或后代。
若 \(u\) 是关键点,显然我们只能将 \(u\) 单独分一个组,一共有 \(x\) 中方案,则:$f_{u,x} = x \times \prod f_{v,x-1} $。
对于答案,考虑枚举分成的组数 \(i\)。根据容斥,有:\(sum_i = \frac{\sum \limits_{j=0}^i (-1)^j \times C_{i}^j \times f_{r,i-j}}{A_{i}^{i}}\)。然后 \(ans = \sum\limits_{i=1}^{m} sum_i\)。
单次询问的复杂度是 \(O(km+k \log n)\) 的。\(m \le 300\),能过。
对于换根的虚树构造,可以将换的 \(r\) 看成一个不记录答案的关键点。跑暴力的时候就可以直接以 \(r\) 为根了。
Smuggling Marbles
很显然的可以发现,不可能有两个深度相同的点同时出现在一个点上。也就是说,不同深度节点对于答案的贡献独立。
考虑对于相同深度的节点建立虚树。定义状态函数 \(f_{i,0/1/2}\) 表示在某一时刻节点 \(i\) 上面有 \(0\) 或 \(1\) 或 \(>2\) 个石子的方案数。
则有转移方程:
对于初始值,显然 \(f_{u,0}=1,f_{u,2}=0\)。因为最开始只有虚树上叶子节点可能有 \(1\) 个石子,所以当 \(u\) 是关键点时 \(f_{u,1}=1\)。
对于答案,除了虚树上的点外,其余的点都可以随便选石子数量,所以每个深度的答案为:\(f_{1,1} \times 2^{n-cnt_{dep}}\)。
复杂度 \(O(n \log n)\)。
Unique Occurrences
咕......
Leaf Color
当时没学过虚树,不然就 AK 了 pwp。
考虑对于相同颜色建立虚树,然后答案就是在每棵虚树上生成出叶子节点都是关键点的树的方案数之和。
定义状态函数 \(f_{i}\) 表示在以 \(i\) 为根的子树中,\(i\) 作为生成树的树根时的方案数。若 \(u\) 是关键点,则 \(v\) 将会有选择与不选择两种情况,即:\(f_u =\prod (f_{v}+1)\)。而对于 \(u\) 不是关键点的情况,显然只选 \(u\)(\(u\) 的儿子都不选)是不行的,所以:\(f_{u} =\prod (f_v +1 ) -1\)。
对于答案的贡献,当 \(u\) 是关键点时显然是 \(f_u\)。当 \(u\) 不是关键点时,若 \(u\) 只与其 \(1\) 个儿子连边生成树,显然 \(u\) 的度数为 \(1\) 了(变成叶子节点了)。所以要将其贡献从 \(f_u\) 中减去 \(\sum f_v\)。
复杂度 \(O(n \log n)\)。

浙公网安备 33010602011771号