最小生成树
前置:
并查集
并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
优化 :
- 按秩合并 : 我们可以将节点较少或深度较小的树连到另一棵
- 路径压缩 : 查询过程中经过的每个元素都属于该集合,我们可以将其直接连到根节点以加快后续查询。
复杂度 : O(n \times alpha(n))
拓展 : 带权并查集
我们还可以在并查集的边上定义某种权值、以及这种权值在路径压缩时产生的运算,从而解决更多的问题。
eg1 :
维护一个 n 长序列, 原都为可用
- 标记某个元素是不可以
- 查询某个元素位置后第一个可用的元素
eg2 : [ABC238E] Range Sums
solution :
我们知道了 sum[r] - sum[l - 1] = k
故我们知道了 sum[l - 1] 就能计算出 sum[r]
故把每一个 l - 1 和 r 放在一个并查集中,
根据 sum[0] = 0 我们就能知道 sum[n]
(前提是 0 和 n 在一个并查集)
正题 :
kruskal
- 思想 : 贪心
- 把所有的边按边权排序
- 以此枚举每条边, 判断是否不连通
- 把这条边加入最小生成树, 合并两并查集
复杂度 : O (m \log (m))
kruskal \text{重构树}
在跑 kruskal 的过程中我们会从小到大加入若干条边。现在我们仍然按照这个顺序。
首先新建 n 个集合,每个集合恰有一个节点,点权为 $0$。
每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权,同时将两个集合的根节点分别设为新建点的左儿子和右儿子。然后我们将两个集合和新建点合并成一个集合。将新建点设为根。
不难发现,在进行 n - 1 轮之后我们得到了一棵恰有 n 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子。这棵树就叫 kruskal 重构树。
性质 :
- 原图中两个点之间的所有简单路径上最大边权的最小值
= 最小生成树上两个点之间的简单路径上的最大值
= kruskal 重构树上两点之间的 LCA 的权值。
- 从 i 出发, 经过的每一条边都不超过 k , 能到达哪些
点
则在重构树上, 找到最后一个 \le k 的 i 的祖先
它的所有子树内的所有节点都可以到达
fa_in_tree[find(x)] = ++ tot;
fa_in_tree[find(y)] = tot;
val[tot] = e[i].val;
prim
它死了
在多点少边时略快于 kruskal
复杂度 : O ((n + m) \log (m))
eg1 : [NOIP2013 提高组] 货车运输
solution :
- 首先重新建图
- 构造出最大生成树
- 然后在最大生成树上求 LCA 来回答询问
eg2 :
一个国家有n个城市, 若干个城市之间有电话线连接,
先要增加 m 条电话线使所有城市直接或间接与其他所有城市相连.
solution:
- 先把已知的被电话线相连两个点加入一个连通块(缩点)
- 跑 kruskal
eg3 : [USACO08OCT] Watering Hole G
solution :
- 建一个超级源点 把每个点与那个超级源点相连, 边权为打井的话费
- 跑 kruskal
eg4 : [USACO08NOV] Cheering up the Cow G
solution :
-
把 i → j 边权改为 w_{i, j} + c_j
-
把 j → i 边权改为 w_{i, j} + c_i
-
合并得 i - j 边权为 2 \cdot w_{i, j} + c_i + c_j
-
跑 kruskal
eg4. [NOI2018] 归程
solution :
-
如果我们以海拔为第一关键字对边进行从大到小的排序,然后修建
kruskal 重构树,这样就弄出了一颗以海拔为关键字的小根堆。然后对于每一棵子树,如果询问中的水位线是低于子树的根节点的,那么此时这棵子树中的所有叶子结点都是连通的。放到题中就是说这颗子树中任选一个点出发,到子树中的其它点都不需要花费。 -
然后我们假设对于当前询问,我们找到了一个子树的根节点 u ,满足
d[u] > p 且 d[fa[u]] \le p 且出发点 v 在子树中,这时从 v 出发可以直接抵达子树中的任意一个叶子结点。因此我们需要从众多叶子节点中选出一个距离 1 号点花费最小的。 -
然后再捋一捋思路。我们首先要求出每个点到 1 号点的最小花费,这个直接 dijstra\ + 最短路预处理。然后是要建出 kruskal 重构树,再然后维护以每个点作为根节点时子树中距离 1 号点的最小花费,这个建完树后一个简单的 dfs 搞定。最后是如何找到点 u ,这时我们要让一个重要的算法登场:倍增算法。直接加上点权 > p 的限制在树上倍增即可。
eg4 : Labyrinth
solution :
由于宽度的变大, 会导致某些边无法通过,在图上就表示为边的断裂
故我们要保证在断开边时形成的两个连通块中
- 其中一个连通块内的所有糖果被吃完
- 且我们在另一个连通块
而在重构树体现于一棵树被砍成两颗子树
我们设 dp[x] 为吃 x 这个连通块所允许的最小初始宽度
那么若断开前先吃完了子树 u:
dp[x] + \sum c_u \le w
dp[x] + \sum c_u \le dp[v]
可得状态转移方程:
dp[x] = max(min(w, dp[v]) - treesum[u],min(w, dp[u]) - treesum[v])
treesum[x] = treesum[u] + treesum[v];
小组成员 : 邵品渊,邵品砚,蒋佳成,顾天泽,马天翔,陈继禹