这几天搞了下 kruskal 重构树,由于之前说过要日更博客,所以过来稍作总结。(其实也没有做太多题目)
引入
给定一张无向图,边有边权,每次询问 \(u\) 到 \(v\) 的所有路径中,边权最大的边最小的边权是多少。
比较显然的做法就是建出最小生成树,然后树剖或者倍增求两点边权最大值。
让我们来考虑另一种思路,在建出最小生成树后进行边分治,每次找出边权最大的边进行分治,如果在某一次分治过程中, \(u\) 和 \(v\) 在断掉当前边后不再联通了,就说明 \(u\) 和 \(v\) 之间的边权最大值就是当前边的边权,正确性显然,但是具体实现我们可以不这样做,我们可以建出边分树,而 \(u\) 和 \(v\) 之间的边权最大值就是它们在边分树上面的 \(\operatorname{lca}\) 。
但是遗憾的是,我们并不能像平常建立边分树一样去建立这棵边分树,否则复杂度就无法保证,考虑到在 kruskal 的过程中,我们是每次找出边权最小的边,然后合并两个连通块,这就像是我们建立这棵边分树的逆过程,所以我们可以在 kruskal 的过程中就顺便建立这棵边分树,每次合并两个连通块的时候就顺便记录一下儿子和父亲就行了,这样复杂度就得到了保证。
这棵边分树,我们也把它叫做 kruskal 重构树。
性质和用途
kruskal 重构树有很多优美的性质:
- 由于 kruskal 重构树是一棵边分树,所以 kruskal 重构树是一棵完整二叉树。
- 任意两点间的 \(\operatorname{lca}\) 就是它们之间的路径中边权最大/最小的边。
- 除去叶子节点(也就是表示点而不是边的节点)外, kruskal 重构树满足堆的性质(即父亲节点边权小于等于/大于等于儿子节点边权)。
由于第二点,询问 \(u\) 到 \(v\) 的所有路径中,边权最大的边最小的边权是多少 kruskal 重构树就可以做到 \(\mathcal O(n\log_2n)\) 预处理 \(\mathcal O(1)\) 查询了,同时记录边的端点的话也是可以很好地得到这条路径是如何组成的(也就是把 \(u\to v\) 分割成了 \(u\to p\to q\to v\) ,其中 \(p,q\) 由一条边连接)。
对于第三点,比较常见的套路就是求一个点 \(u\) 仅经过边权大于/小于 \(x\) 能到达的连通块,实现方法就是倍增,找到深度最低且边权大于/小于 \(x\) 在 kruskal 重构树上面的点,那么以这个点为根的子树里面的所有点 \(u\) 都可以到达,正确性显然,找到连通块之后就是和其他若干算法/数据结构结合了。
一般知道这个套路就可以做题了,其他作用或者套路由于本人阅历太少暂时还没有发现。
一些拓展
kruskal 重构树是边分树,如果不是边有边权而是点有点权那么我们可不可以类似地建出点分树呢?答案是显然的,建立方法和 kruskal 重构树一样,使用逆过程建立,这样我们就得到了一棵类似 kruskal 重构树的点分树,除了第一点性质,后面两点性质是类似的。
末尾
鄙人不才,做了个课件,大家可以看一看(大概是把博客抄了一遍?课件里面讲得更详细一点):
链接: https://pan.baidu.com/s/1w_isCJBUaI73sMjFfYbOyQ 提取码: rswr