在阅读下列内容之前,请务必阅读 [图论基础](/graph/basic) 与 [树基础](/graph/tree-basic) 部分,并了解以下定义:
-1. 生成子图
-2. 生成树
+1. 生成子图
+2. 生成树
-我们定义无向连通图的**最小生成树**(Minimum Spanning Tree,MST)为边权和最小的生成树。
+我们定义无向连通图的 **最小生成树** (Minimum Spanning Tree,MST)为边权和最小的生成树。
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。
## Boruvka 算法
-接下来介绍另一种求解最小生成树的算法——Boruvka 算法。该算法的思想是前两种算法的结合。它可以用于求解**边权互不相同**的无向连通图的最小生成树。
+接下来介绍另一种求解最小生成树的算法——Boruvka 算法。该算法的思想是前两种算法的结合。它可以用于求解 **边权互不相同** 的无向连通图的最小生成树。
为了描述该算法,我们需要引入一些定义:
-1. 对无向连通图$G=(V,E)$,定义$T=(V,E')$表示其对应的最小生成树,其中$V$表示点集(与原图的点集等价),$E'$是边集,$E'\in E$。
-2. 在算法执行过程中,我们逐步向$E'$加边,因此定义最小生成森林的**连通块**表示一个点集$V'\in V$,且这个点集中的任意两个点$u,v$在$E'$的边构成的子图上是连通的(互相可达)。
+1. 对无向连通图 $G=(V,E)$ ,定义 $T=(V,E')$ 表示其对应的最小生成树,其中 $V$ 表示点集(与原图的点集等价), $E'$ 是边集, $E'\in E$ 。
+2. 在算法执行过程中,我们逐步向 $E'$ 加边,因此定义最小生成森林的 **连通块** 表示一个点集 $V'\in V$ ,且这个点集中的任意两个点 $u,v$ 在 $E'$ 的边构成的子图上是连通的(互相可达)。
-初始时,$E'=\varnothing$,每个点各自是一个连通块:
+初始时, $E'=\varnothing$ ,每个点各自是一个连通块:
-1. 遍历每一个连通块$U$,考虑所有与该连通块相连且不在其内部的边$(u,v),[u\in U \oplus v\in U]$(中括号内的$\oplus$即异或,表示两个条件有且仅有一个成立)。假设这些边组成的集合为$E_U$,则我们找到$E_U$中权值最小的边。根据题设条件,它是唯一的。然后我们将其标记为**已选择**。有时候你找到的边可能已被标记,那么你就直接忽略(不需要去找第二小的边什么的);
-2. 遍历完当前状态下的所有连通块之后,就把所有标记为**已选择**的边都加到$E'$中,并更新连通块状态。
-3. 如果连通块数量大于1,返回步骤1;否则退出。
+1. 遍历每一个连通块 $U$ ,考虑所有与该连通块相连且不在其内部的边 $(u,v),[u\in U \oplus v\in U]$ (中括号内的 $\oplus$ 即异或,表示两个条件有且仅有一个成立)。假设这些边组成的集合为 $E_U$ ,则我们找到 $E_U$ 中权值最小的边。根据题设条件,它是唯一的。然后我们将其标记为 **已选择** 。有时候你找到的边可能已被标记,那么你就直接忽略(不需要去找第二小的边什么的);
+2. 遍历完当前状态下的所有连通块之后,就把所有标记为 **已选择** 的边都加到 $E'$ 中,并更新连通块状态。
+3. 如果连通块数量大于 1,返回步骤 1;否则退出。
-下面通过一张动态图来举一个例子(图源自[维基百科](https://en.wikipedia.org/wiki/Bor%C5%AFvka%27s_algorithm)):
+下面通过一张动态图来举一个例子(图源自 [维基百科](https://en.wikipedia.org/wiki/Bor%C5%AFvka%27s_algorithm) ):
![](./images/mst-1.gif)
-可以证明,算法只会迭代不超过$O(\log_2V)$次,因此算法复杂度是$O(E\log_2V)$的。给出算法的伪代码:
+可以证明,算法只会迭代不超过 $O(\log_2V)$ 次,因此算法复杂度是 $O(E\log_2V)$ 的。给出算法的伪代码:
```text
Input: A graph G whose edges have distinct weights
## 习题
-- [「HAOI2006」聪明的猴子](https://www.lydsy.com/JudgeOnline/problem.php?id=2429)
-- [「SCOI2005」繁忙的都市](https://www.lydsy.com/JudgeOnline/problem.php?id=1083)
+- [「HAOI2006」聪明的猴子](https://www.lydsy.com/JudgeOnline/problem.php?id=2429)
+- [「SCOI2005」繁忙的都市](https://www.lydsy.com/JudgeOnline/problem.php?id=1083)
## 最小生成树的唯一性
```cpp
#include <algorithm>
#include <cstdio>
-
+
struct Edge {
int x, y, z;
};
const long long INF64 = 0x3fffffffffffffffLL;
struct Edge {
- int u, v, val;
- bool operator<(const Edge &other) const { return val < other.val; }
+ int u, v, val;
+ bool operator<(const Edge &other) const { return val < other.val; }
};
Edge e[300010];
long long sum;
class Tr {
-private:
- struct Edge {
- int to, nxt, val;
- } e[600010];
- int cnt, head[100010];
-
- int pnt[100010][22];
- int dpth[100010];
- // 到祖先的路径上边权最大的边
- int maxx[100010][22];
- // 到祖先的路径上边权次大的边,若不存在则为 -INF
- int minn[100010][22];
-
-public:
- void addedge(int u, int v, int val) {
- e[++cnt] = (Edge){v, head[u], val};
- head[u] = cnt;
- }
-
- void insedge(int u, int v, int val) {
- addedge(u, v, val);
- addedge(v, u, val);
+ private:
+ struct Edge {
+ int to, nxt, val;
+ } e[600010];
+ int cnt, head[100010];
+
+ int pnt[100010][22];
+ int dpth[100010];
+ // 到祖先的路径上边权最大的边
+ int maxx[100010][22];
+ // 到祖先的路径上边权次大的边,若不存在则为 -INF
+ int minn[100010][22];
+
+ public:
+ void addedge(int u, int v, int val) {
+ e[++cnt] = (Edge){v, head[u], val};
+ head[u] = cnt;
+ }
+
+ void insedge(int u, int v, int val) {
+ addedge(u, v, val);
+ addedge(v, u, val);
+ }
+
+ void dfs(int now, int fa) {
+ dpth[now] = dpth[fa] + 1;
+ pnt[now][0] = fa;
+ minn[now][0] = -INF;
+ for (int i = 1; (1 << i) <= dpth[now]; i++) {
+ pnt[now][i] = pnt[pnt[now][i - 1]][i - 1];
+ int kk[4] = {maxx[now][i - 1], maxx[pnt[now][i - 1]][i - 1],
+ minn[now][i - 1], minn[pnt[now][i - 1]][i - 1]};
+ // 从四个值中取得最大值
+ std::sort(kk, kk + 4);
+ maxx[now][i] = kk[3];
+ // 取得严格次大值
+ int ptr = 2;
+ while (ptr >= 0 && kk[ptr] == kk[3]) ptr--;
+ minn[now][i] = (ptr == -1 ? -INF : kk[ptr]);
}
- void dfs(int now, int fa) {
- dpth[now] = dpth[fa] + 1;
- pnt[now][0] = fa;
- minn[now][0] = -INF;
- for (int i = 1; (1 << i) <= dpth[now]; i++) {
- pnt[now][i] = pnt[pnt[now][i - 1]][i - 1];
- int kk[4] = {maxx[now][i - 1], maxx[pnt[now][i - 1]][i - 1],
- minn[now][i - 1], minn[pnt[now][i - 1]][i - 1]};
- // 从四个值中取得最大值
- std::sort(kk, kk + 4);
- maxx[now][i] = kk[3];
- // 取得严格次大值
- int ptr = 2;
- while (ptr >= 0 && kk[ptr] == kk[3]) ptr--;
- minn[now][i] = (ptr == -1 ? -INF : kk[ptr]);
- }
-
- for (int i = head[now]; i; i = e[i].nxt) {
- if (e[i].to != fa) {
- maxx[e[i].to][0] = e[i].val;
- dfs(e[i].to, now);
- }
- }
+ for (int i = head[now]; i; i = e[i].nxt) {
+ if (e[i].to != fa) {
+ maxx[e[i].to][0] = e[i].val;
+ dfs(e[i].to, now);
+ }
}
+ }
- int lca(int a, int b) {
- if (dpth[a] < dpth[b]) std::swap(a, b);
+ int lca(int a, int b) {
+ if (dpth[a] < dpth[b]) std::swap(a, b);
- for (int i = 21; i >= 0; i--)
- if (dpth[pnt[a][i]] >= dpth[b]) a = pnt[a][i];
+ for (int i = 21; i >= 0; i--)
+ if (dpth[pnt[a][i]] >= dpth[b]) a = pnt[a][i];
- if (a == b) return a;
+ if (a == b) return a;
- for (int i = 21; i >= 0; i--) {
- if (pnt[a][i] != pnt[b][i]) {
- a = pnt[a][i];
- b = pnt[b][i];
- }
- }
- return pnt[a][0];
+ for (int i = 21; i >= 0; i--) {
+ if (pnt[a][i] != pnt[b][i]) {
+ a = pnt[a][i];
+ b = pnt[b][i];
+ }
}
-
- int query(int a, int b, int val) {
- int res = -INF;
- for (int i = 21; i >= 0; i--) {
- if (dpth[pnt[a][i]] >= dpth[b]) {
- if (val != maxx[a][i])
- res = std::max(res, maxx[a][i]);
- else
- res = std::max(res, minn[a][i]);
- a = pnt[a][i];
- }
- }
- return res;
+ return pnt[a][0];
+ }
+
+ int query(int a, int b, int val) {
+ int res = -INF;
+ for (int i = 21; i >= 0; i--) {
+ if (dpth[pnt[a][i]] >= dpth[b]) {
+ if (val != maxx[a][i])
+ res = std::max(res, maxx[a][i]);
+ else
+ res = std::max(res, minn[a][i]);
+ a = pnt[a][i];
+ }
}
+ return res;
+ }
} tr;
int fa[100010];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void Kruskal() {
- int tot = 0;
- std::sort(e + 1, e + m + 1);
- for (int i = 1; i <= n; i++) fa[i] = i;
-
- for (int i = 1; i <= m; i++) {
- int a = find(e[i].u);
- int b = find(e[i].v);
- if (a != b) {
- fa[a] = b;
- tot++;
- tr.insedge(e[i].u, e[i].v, e[i].val);
- sum += e[i].val;
- used[i] = 1;
- }
- if (tot == n - 1) break;
+ int tot = 0;
+ std::sort(e + 1, e + m + 1);
+ for (int i = 1; i <= n; i++) fa[i] = i;
+
+ for (int i = 1; i <= m; i++) {
+ int a = find(e[i].u);
+ int b = find(e[i].v);
+ if (a != b) {
+ fa[a] = b;
+ tot++;
+ tr.insedge(e[i].u, e[i].v, e[i].val);
+ sum += e[i].val;
+ used[i] = 1;
}
+ if (tot == n - 1) break;
+ }
}
int main() {
- std::ios::sync_with_stdio(0);
- std::cin.tie(0);
- std::cout.tie(0);
-
- std::cin >> n >> m;
- for (int i = 1; i <= m; i++) {
- int u, v, val;
- std::cin >> u >> v >> val;
- e[i] = (Edge){u, v, val};
- }
+ std::ios::sync_with_stdio(0);
+ std::cin.tie(0);
+ std::cout.tie(0);
- Kruskal();
- long long ans = INF64;
- tr.dfs(1, 0);
-
- for (int i = 1; i <= m; i++) {
- if (!used[i]) {
- int _lca = tr.lca(e[i].u, e[i].v);
- // 找到路径上不等于 e[i].val 的最大边权
- long long tmpa = tr.query(e[i].u, _lca, e[i].val);
- long long tmpb = tr.query(e[i].v, _lca, e[i].val);
- // 这样的边可能不存在,只在这样的边存在时更新答案
- if (std::max(tmpa, tmpb) > -INF)
- ans = std::min(ans, sum - std::max(tmpa, tmpb) + e[i].val);
- }
+ std::cin >> n >> m;
+ for (int i = 1; i <= m; i++) {
+ int u, v, val;
+ std::cin >> u >> v >> val;
+ e[i] = (Edge){u, v, val};
+ }
+
+ Kruskal();
+ long long ans = INF64;
+ tr.dfs(1, 0);
+
+ for (int i = 1; i <= m; i++) {
+ if (!used[i]) {
+ int _lca = tr.lca(e[i].u, e[i].v);
+ // 找到路径上不等于 e[i].val 的最大边权
+ long long tmpa = tr.query(e[i].u, _lca, e[i].val);
+ long long tmpb = tr.query(e[i].v, _lca, e[i].val);
+ // 这样的边可能不存在,只在这样的边存在时更新答案
+ if (std::max(tmpa, tmpb) > -INF)
+ ans = std::min(ans, sum - std::max(tmpa, tmpb) + e[i].val);
}
- // 次小生成树不存在时输出 -1
- std::cout << (ans == INF64 ? -1 : ans) << '\n';
- return 0;
+ }
+ // 次小生成树不存在时输出 -1
+ std::cout << (ans == INF64 ? -1 : ans) << '\n';
+ return 0;
}
```