树上启发式合并
泛化ψ(`∇´)ψ
启发式合并可以被归类为“用脑子优化”。
比如常见的并查集按秩合并,就是一种启发式合并:
1 2 3 4 5 6 7 |
|
通常来说我们不能感性的去证明这类优化的复杂度,我们需要理性的分析。
比如并查集按秩合并,虽然但是,我并不会证明这个东西的复杂度,但是可以看一下论文:Tarjan, R. E., & Van Leeuwen, J. (1984). Worst-case analysis of set union algorithms. Journal of the ACM (JACM), 31(2), 245-281。
但这类问题的思想都是大同小异的,通过安排某种特定的合并顺序,使得合并的基本操作数/复杂度能够减少。
对于 dsu on tree(称作树上启发式合并应当更恰当),通常我们所遇到的是一类静态的查询子树信息的问题。
对于暴力做法,我们需要直接遍历所有子树考虑求出答案,也不做任何继承,复杂度是 \(O(n^2)\) 的。(假定一次合并为 \(O(1)\)。)
我们注意到做继承实际上很麻烦,为了节省空间,我们一般使用的是一个全局数组统计答案,递归下去很不好计算这个全局数组的变化。
而我们不妨考虑一个优化,我们先把所有轻儿子的答案算出来,但是对于当前节点的贡献我们并不保留。
然后我们递归下去算重儿子的贡献,并保留它对全局数组的影响,然后再加入轻儿子的影响,这看起来和原来没什么差别。
但实际上,重儿子的影响被直接继承了,而我们暴力合并的都是轻儿子的影响,我们不妨分析一些这个过程。
从重链剖分那里我们能知道,任意一个节点到根节点的轻边级别为 \(O(\log n)\),具体来说设这个点子树大小为 \(siz\),经过了 \(l\) 条轻边。
因为轻边连接的子树不会超过子树的一半,所以每过一个轻边子树就会折半,那么 \(siz \le n / 2^l\),所以 \(l\) 的级别是 \(\log n\) 的。
然后,因为我们合并的时候跳过了重儿子,所以每个节点的被访问次数就是 \(l + 1\),那么每个节点均摊会被访问 \(\log n\) 次。
总复杂度就变成了 \(O(n \log n)\)。
例题ψ(`∇´)ψ
子树数颜色个数ψ(`∇´)ψ
题面就这么简单,没啥好说的,可以在这里交。
做法就是我们上面所提到的,维护一个全局 cnt 数组即可,然后因为我们实际上做了一个重链剖分,所以加入整个子树不用递归直接 dfn 扫一下就好,常数会小一点。
dfs 可以记录一个变量表示当前是否保留,就不用多写 dfs 了。
Last mile of the wayψ(`∇´)ψ
神秘来源,我不知道哪里可以交,这是我捡到的题
给定你一棵树,每一个节点有一个权值 \(w\) 和一个体积 \(a\)。
有 \(q\) 次询问,每次询问形如 \(x, s\),
表示询问在以 \(x\) 为根的子树中,选不超过 \(s\) 的节点,权值和的最大值。
这里没有任何依赖关系,相当于把这个子树里的所有节点提出来当成一个序列来取。
\(1\le n \le 5\times 10^3, q\le 10^5, w_i \le 10^6, a,x,s \le 5\times 10^3\)。
暴力做法就是直接一个形如树上背包的东西。
然后我们还是考虑 dsu on tree 优化,重儿子直接用,轻儿子再做一次 dp 暴力加。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
|