【笔记】启发式合并
来自\(\texttt{SharpnessV}\)的省选复习计划中的启发式合并。
启发式合并用于解决这样一类问题
给定\(n\)个集合,每个集合开始只有\(1\)个数,你需要支持每次合并两个集合
直接做显然是\(N^2\)的,但我们可以稍加优化。
每次合并,我们都选择较小的一个集合,将它合并到较大的集合上。
看上去本质上没有改变,但时间复杂度直接由 \(N^2\) 降低至 \(N\log\) 。
为什么复杂度是 $N \log $ 的,因为对于每个元素,每次合并一定是从小的集合合并到大的集合,所以合并后的集合大小一定翻倍,那么一个元素最多移动 \(\log\) 次,总的时间复杂度为 \(N\log\) 。
简述:给定 \(N\) 个初始有 \(1\) 个元素的栈,每次支持将一个栈合并到另一个栈(按栈的出入顺序)。
直接模拟不难做到\(N^2\)的复杂度。
观察一下发现这就是启发式合并的模型,考虑用启发式合并解决。
我们用双端队列模拟一个栈,开始队首对应栈顶。
启发式合并时,发现栈 \(X\to Y\)得到的栈正好是 \(Y\to X\)得到的栈的反序,所以我们对双端队列记录一个\(op\)表示队列里的元素是否反转。
简述:支持连边操作和路径第\(k\)小操作,强制在线。
如果不强制在线我们可以将最终的树建出来,然后直接可持久化线段树。
如果强制在线,我们可以考虑动态建树,但由于连边后根会移动,所以我们需要对整棵树重新构建。
但观察一下可以发现,我们合并\(X,Y\)两棵树时,有一棵树可以不用改变,所以合并时我们直接暴力重构较小的树,时空复杂度为 \(\rm O(N\log^2 N)\)。
不难发现对于一条直上直下的单链,有多少点就要划分多少段。
所以划分段数就是子树深度,我们用堆维护这些分段,然后启发式合并即可。
时间复杂度\(\rm O(N\log^2N)\)。
简述:支持合并联通块和查询联通块第\(k\)小。
用平衡树维护联通块中所以值,然后直接启发式合并即可。
简述:可持久化并查集
由于可持久化后不便于路径压缩,所以考虑启发式合并。并查集按深度启发式合并,同样可以得到\(\rm O(N\log N)\)的时间复杂度。
用可持久化线段树实现可持久化数组,然后时限并查集,时间复杂度\(\rm O(N\log^2 N)\)。
树上启发式合并,简称 DSU on Tree
。
解决一类静态子树问题,我们按照以下顺序。
- 计算所有轻儿子贡献,并删除贡献
- 计算重儿子贡献,保留贡献
- 将轻儿子贡献合并到重儿子中
- 回答当前子树询问,回溯
不难发现一个节点会在重儿子的子树中总共计算一次,在每个轻儿子的子树中计算\(1\)次,但一个节点到根的作为轻儿子的次数不超过\(\log N\),所以时间复杂度为\(\rm O(N\log N)\)。
模板题,类似莫队开一个桶记录每种颜色出现次数,然后套用上面的套路即可。
需要一些技巧的 DSU ,由于要统计所有路径,我们可以类似点分治先递归计算所有子树中路劲的最大值,再计算以当前点为 LCA 的路径。
计算当前点的路径,我们开一个桶记录路径异或值为\(x\)的所有路径中,能够达到的最大深度是多少。然后在合并子树时统计答案即可。
我们仍然按照套路合并子树,这样时间复杂度是\(\rm O(N\log N\log maxA_i)\)。