12345678910Input. The edges of the graph e, where each element in e is (u,v,w) denoting that there is an edge between u and v weighted w.Output. The edges of the MST of the input graph.Method. result←∅sort e into nondecreasing order by weight wfor each (u,v,w) in the sorted eif u and v are not connected in the union-find set connect u and v in the union-find setresult←result⋃{(u,v,w)}return result
有 n 朵云,你要将它们连成 k 个棉花糖,将 Xi 云朵和 Yi 连接起来需要 Li 的代价,求最小代价。
例题代码
[list2tab]
C++
#include <algorithm>#include <iostream>using namespace std;int fa[1010]; // 定义父亲int n, m, k;struct edge { int u, v, w;};int l;edge g[10010];void add(int u, int v, int w) { l++; g[l].u = u; g[l].v = v; g[l].w = w;}// 标准并查集int findroot(int x) { return fa[x] == x ? x : fa[x] = findroot(fa[x]); }void Merge(int x, int y) { x = findroot(x); y = findroot(y); fa[x] = y;}bool cmp(edge A, edge B) { return A.w < B.w; }// Kruskal 算法void kruskal() { int tot = 0; // 存已选了的边数 int ans = 0; // 存总的代价 for (int i = 1; i <= m; i++) { int xr = findroot(g[i].u), yr = findroot(g[i].v); if (xr != yr) { // 如果父亲不一样 Merge(xr, yr); // 合并 tot++; // 边数增加 ans += g[i].w; // 代价增加 if (tot == n - k) { // 检查选的边数是否满足 k 个棉花糖 cout << ans << '\n'; return; } } } cout << "No Answer\n"; // 无法连成}int main() { cin >> n >> m >> k; if (n == k) { // 特判边界情况 cout << "0\n"; return 0; } for (int i = 1; i <= n; i++) { // 初始化 fa[i] = i; } for (int i = 1; i <= m; i++) { int u, v, w; cin >> u >> v >> w; add(u, v, w); // 添加边 } sort(g + 1, g + m + 1, cmp); // 先按边权排序 kruskal(); return 0;}
Python
class Edge: def __init__(self, u, v, w): self.u = u self.v = v self.w = wfa = [0] * 1010 # 定义父亲g = []def add(u, v, w): g.append(Edge(u, v, w))# 标准并查集def findroot(x): if fa[x] == x: return x fa[x] = findroot(fa[x]) return fa[x]def Merge(x, y): x = findroot(x) y = findroot(y) fa[x] = y# Kruskal 算法def kruskal(): tot = 0 # 存已选了的边数 ans = 0 # 存总的代价 for e in g: x = findroot(e.u) y = findroot(e.v) if x != y: # 如果父亲不一样 fa[x] = y # 合并 tot += 1 # 边数增加 ans += e.w # 代价增加 if tot == n - k: # 检查选的边数是否满足 k 个棉花糖 print(ans) return print("No Answer") # 无法连成if __name__ == "__main__": n, m, k = map(int, input().split()) if n == k: # 特判边界情况 print("0") exit() for i in range(1, n + 1): # 初始化 fa[i] = i for i in range(1, m + 1): u, v, w = map(int, input().split()) add(u, v, w) # 添加边 g.sort(key=lambda edge: edge.w) # 先按边权排序 kruskal()
Java
import java.util.Arrays;import java.util.Scanner;class Edge { int u; int v; int w; Edge(int u, int v, int w) { this.u = u; this.v = v; this.w = w; }}public class Main { static int[] parent = new int[1010]; // 定义父亲 static int m, n, k; // n 表示点的数量, m 表示边的数量,k 表示需要的棉花糖个数 static Edge[] edges = new Edge[10010]; static int l; static void addEdge(int u, int v, int w) { edges[++l] = new Edge(u, v, w); } // 标准并查集 static int findroot(int x) { if (parent[x] != x) { parent[x] = findroot(parent[x]); } return parent[x]; } static void Merge(int x, int y) { x = findroot(x); y = findroot(y); parent[x] = y; } static boolean cmp(Edge A, Edge B) { return A.w < B.w; } // Kruskal 算法 static void kruskal() { int tot = 0; // 存已选了的边数 int ans = 0; // 存总的代价 for (int i = 1; i <= m; i++) { int xr = findroot(edges[i].u); int yr = findroot(edges[i].v); if (xr != yr) { // 如果父亲不一样 Merge(xr, yr); // 合并 tot++; // 边数增加 ans += edges[i].w; // 代价增加 if (tot == n - k) { // 检查选的边数是否满足 k 个棉花糖 System.out.println(ans); return; } } } System.out.println("No Answer"); // 无法连成 } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); m = scanner.nextInt(); k = scanner.nextInt(); if (n == k) { // 特判边界情况 System.out.println("0"); return; } // 初始化 for (int i = 1; i <= n; i++) { parent[i] = i; } for (int i = 1; i <= m; i++) { int u = scanner.nextInt(); int v = scanner.nextInt(); int w = scanner.nextInt(); addEdge(u, v, w); // 添加边 } Arrays.sort(edges, 1, m + 1, (a, b) -> Integer.compare(a.w, b.w)); // 先按边权排序 kruskal(); scanner.close(); }}
Prim 算法
Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开始,不断加点(而不是 Kruskal 算法的加边)。
12345678910111213141516Input. The nodes of the graph V ; the function g(u,v) whichmeans the weight of the edge (u,v); the function adj(v) whichmeans the nodes adjacent to v.Output. The sum of weights of the MST of the input graph.Method.result←0choose an arbitrary node in V to be the rootdis(root)←0for each node v∈(V−{root})dis(v)←∞rest←Vwhile rest=∅cur←the node with the minimum dis in restresult←result+dis(cur)rest←rest−{cur}for each node v∈adj(cur)dis(v)←min(dis(v),g(cur,v))return result
// 使用二叉堆优化的 Prim 算法。#include <cstring>#include <iostream>#include <queue>using namespace std;constexpr int N = 5050, M = 2e5 + 10;struct E { int v, w, x;} e[M * 2];int n, m, h[N], cnte;void adde(int u, int v, int w) { e[++cnte] = E{v, w, h[u]}, h[u] = cnte; }struct S { int u, d;};bool operator<(const S &x, const S &y) { return x.d > y.d; }priority_queue<S> q;int dis[N];bool vis[N];int res = 0, cnt = 0;void Prim() { memset(dis, 0x3f, sizeof(dis)); dis[1] = 0; q.push({1, 0}); while (!q.empty()) { if (cnt >= n) break; int u = q.top().u, d = q.top().d; q.pop(); if (vis[u]) continue; vis[u] = true; ++cnt; res += d; for (int i = h[u]; i; i = e[i].x) { int v = e[i].v, w = e[i].w; if (w < dis[v]) { dis[v] = w, q.push({v, w}); } } }}int main() { cin >> n >> m; for (int i = 1, u, v, w; i <= m; ++i) { cin >> u >> v >> w, adde(u, v, w), adde(v, u, w); } Prim(); if (cnt == n) cout << res; else cout << "No MST."; return 0;}
1234567891011121314151617Input. A graph G whose edges have distinct weights. Output. The minimum spanning forest of G.Method. Initialize a forest F to be a set of one-vertex treeswhile TrueFind the components of F and label each vertex of G by its component Initialize the cheapest edge for each component to "None"for each edge (u,v) of Gif u and v have different component labelsif (u,v) is cheaper than the cheapest edge for the component of u Set (u,v) as the cheapest edge for the component of uif (u,v) is cheaper than the cheapest edge for the component of v Set (u,v) as the cheapest edge for the component of vif all components’cheapest edges are "None"return Ffor each component whose cheapest edge is not "None" Add its cheapest edge to F
#include <algorithm>#include <iostream>struct Edge { int x, y, z;};int f[100001];Edge a[100001];int cmp(const Edge& a, const Edge& b) { return a.z < b.z; }int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }using std::cin;using std::cout;int main() { cin.tie(nullptr)->sync_with_stdio(false); int t; cin >> t; while (t--) { int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) f[i] = i; for (int i = 1; i <= m; i++) cin >> a[i].x >> a[i].y >> a[i].z; std::sort(a + 1, a + m + 1, cmp); // 先排序 int num = 0, ans = 0, tail = 0, sum1 = 0, sum2 = 0; bool flag = true; for (int i = 1; i <= m + 1; i++) { // 再并查集加边 if (i > tail) { if (sum1 != sum2) { flag = false; break; } sum1 = 0; for (int j = i; j <= m + 1; j++) { if (j > m || a[j].z != a[i].z) { tail = j - 1; break; } if (find(a[j].x) != find(a[j].y)) ++sum1; } sum2 = 0; } if (i > m) break; int x = find(a[i].x); int y = find(a[i].y); if (x != y && num != n - 1) { sum2++; num++; f[x] = f[y]; ans += a[i].z; } } if (flag) cout << ans << '\n'; else cout << "Not Unique!\n"; } return 0;}
次小生成树
非严格次小生成树
定义
在无向图中,边权和最小的满足边权和 大于等于 最小生成树边权和的生成树
求解方法
求出无向图的最小生成树 T,设其权值和为 M
遍历每条未被选中的边 e=(u,v,w),找到 T 中 u 到 v 路径上边权最大的一条边 e′=(s,t,w′),则在 T 中以 e 替换 e′,可得一棵权值和为 M′=M+w−w′ 的生成树 T′.
#include <algorithm>#include <iostream>constexpr int INF = 0x3fffffff;constexpr long long INF64 = 0x3fffffffffffffffLL;struct Edge { int u, v, val; bool operator<(const Edge &other) const { return val < other.val; }};Edge e[300010];bool used[300010];int n, m;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); } 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); } } } 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]; 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]; } 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] = true; } if (tot == n - 1) break; }}int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); 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;}