|
| 1 | +# 3600. 升级后最大生成树稳定性 |
| 2 | + |
| 3 | +> **日期**:2026-03-12 |
| 4 | +> **所用时间**:45min |
| 5 | +> **知识点**:并查集、二分答案 |
| 6 | +
|
| 7 | +## 1. 并查集 + 二分答案 |
| 8 | + |
| 9 | +### 题意与目标 |
| 10 | + |
| 11 | +- 无向图有 $n$ 个点、若干条边,每条边有**稳定性** $s$,且可能是**必选**或**可选**。 |
| 12 | +- 可以花费 1 次机会将某条可选边的稳定性从 $s$ 升级为 $2s$,最多升级 $k$ 次。 |
| 13 | +- 求:在形成一棵生成树的前提下,**生成树中边的最小稳定性**(即树的稳定性)最大能是多少;若无法形成生成树则返回 $-1$。 |
| 14 | + |
| 15 | +### 思路概述 |
| 16 | + |
| 17 | +答案具有单调性:若稳定性 $x$ 能达到,则比 $x$ 更小的稳定性也能达到。因此可以**二分答案**:二分「最终生成树的最小稳定性」$mid$,用 `check(mid)` 判断:能否在至多 $k$ 次升级内,选边形成一棵生成树,且每条边的稳定性都 $\ge mid$。 |
| 18 | + |
| 19 | +二分范围:下界为所有边中最小的 $s$(`min_s`),上界为「某条边升级后的最大稳定性」$2 \cdot \max_s$,因此取 `[min_s, max_s * 2 + 1)` 即可(右开便于二分)。 |
| 20 | + |
| 21 | +### check(mid) 的逻辑 |
| 22 | + |
| 23 | +要判断「能否在至多 $k$ 次升级下,得到一棵稳定性均 $\ge mid$ 的生成树」,可以按边分类处理: |
| 24 | + |
| 25 | +1. **必选边** |
| 26 | + - 若某条必选边的 $s < mid$,说明即使用上这条边,稳定性也达不到 $mid$,直接返回 `False`。 |
| 27 | + - 否则($s \ge mid$)必须加入生成树,在并查集中合并两端点。 |
| 28 | + |
| 29 | +2. **可选边** |
| 30 | + - $s \ge mid$:不升级也能用,直接加入并查集。 |
| 31 | + - $s < mid \le 2s$:升级一次后稳定性变为 $2s \ge mid$,可以选用,但会消耗 1 次升级机会。 |
| 32 | + - $mid > 2s$:升级后仍达不到 $mid$,不能选。 |
| 33 | + |
| 34 | +实现时可以先**第一遍扫所有边**:必选边若 $s < mid$ 则返回 `False`;否则对「必选边」或「$s \ge mid$ 的边」执行合并。再**第二遍扫所有边**:只处理可选边且 $s < mid \le 2s$ 的,在升级次数 $t \le k$ 且能合并(不形成环)时合并并 `t -= 1`。最后看并查集是否只剩一个连通分量(`u.cnt == 1`),是则说明能形成一棵满足条件的生成树。 |
| 35 | + |
| 36 | +### 主流程与预处理 |
| 37 | + |
| 38 | +- **无解**: |
| 39 | + - 必选边成环(同一并查集里再合并必选边会失败)则无解。 |
| 40 | + - 把所有边(必选+可选)都加入另一个并查集后,若图不连通(`all_uf.cnt > 1`)则无法形成生成树,也无解。 |
| 41 | +- **有解**:在 `[min_s, max_s * 2 + 1)` 上二分最大的 $mid$ 使得 `check(mid)` 为真,该 $mid$ 即为答案。 |
| 42 | + |
| 43 | +### 复杂度分析 |
| 44 | + |
| 45 | +- **时间复杂度**:预处理并查集与最值 $O(E \cdot \alpha(V))$;二分 $O(\log S)$ 次,每次 `check` 遍历边并查集合并 $O(E \cdot \alpha(V))$,合计 $O(E \cdot \alpha(V) \cdot \log S)$,$S$ 为稳定性上界(如 `max_s * 2`)。 |
| 46 | +- **空间复杂度**:并查集 $O(V)$,以及若干变量 $O(1)$。 |
| 47 | + |
| 48 | +**Python3** |
| 49 | + |
| 50 | +```python |
| 51 | +class UnionFind: |
| 52 | + def __init__(self, n): |
| 53 | + self.fa = list(range(n)) |
| 54 | + self.cnt = n |
| 55 | + |
| 56 | + def find(self, x): |
| 57 | + if self.fa[x] != x: |
| 58 | + self.fa[x] = self.find(self.fa[x]) |
| 59 | + return self.fa[x] |
| 60 | + |
| 61 | + def merge(self, a, b): |
| 62 | + a, b = self.find(a), self.find(b) |
| 63 | + if a == b: |
| 64 | + return False |
| 65 | + self.fa[a] = b |
| 66 | + self.cnt -= 1 |
| 67 | + return True |
| 68 | + |
| 69 | +class Solution: |
| 70 | + def maxStability(self, n: int, edges: List[List[int]], k: int) -> int: |
| 71 | + all_uf = UnionFind(n) |
| 72 | + must_uf = UnionFind(n) |
| 73 | + min_s, max_s = inf, 0 |
| 74 | + for x, y, s, must in edges: |
| 75 | + if must and not must_uf.merge(x, y): |
| 76 | + return -1 |
| 77 | + all_uf.merge(x, y) |
| 78 | + min_s = min(min_s, s) |
| 79 | + max_s = max(max_s, s) |
| 80 | + |
| 81 | + if all_uf.cnt > 1: |
| 82 | + return -1 |
| 83 | + |
| 84 | + def check(mid): |
| 85 | + u = UnionFind(n) |
| 86 | + for x, y, s, must in edges: |
| 87 | + if must and s < mid: |
| 88 | + return False |
| 89 | + if must or s >= mid: |
| 90 | + u.merge(x, y) |
| 91 | + |
| 92 | + t = k |
| 93 | + for x, y, s, must in edges: |
| 94 | + if t == 0 or u.cnt == 1: |
| 95 | + break |
| 96 | + if not must and s < mid <= s * 2 and u.merge(x, y): |
| 97 | + t -= 1 |
| 98 | + return u.cnt == 1 |
| 99 | + |
| 100 | + l, r = min_s, max_s * 2 + 1 |
| 101 | + while l < r: |
| 102 | + mid = l + r + 1 >> 1 |
| 103 | + if check(mid): |
| 104 | + l = mid |
| 105 | + else: |
| 106 | + r = mid - 1 |
| 107 | + return r |
| 108 | +``` |
0 commit comments