對於圖 $ G = (V,E) $, 有 \(n\) 個點, \(m\) 條邊, 由 \(V\) 中全部 \(n\) 個點和 \(E\) 中 \(n-1\) 條邊構成的一個連通子圖(即一棵樹),稱爲 \(G\) 的一個生成樹, 邊權值最小的爲最小生成樹.ios
通常用於稠密圖:算法
#include <iostream> #include <cstring> using namespace std; const int INF = 0x3f3f3f3f; const int N = 510; int n,m; int g[N][N]; //稠密圖 int dist[N]; //表示某個結點到當前集合的最小距離(與dijkstra不一樣) int st[N]; //是否在集合內 int prim() { memset(dist, INF, sizeof dist); int res = 0; for (int i = 0; i < n; i ++ ){ int t = -1; for (int j = 1; j <= n; j ++ ) //尋找不在集合內,且到集合距離最小的結點 if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; st[t] = 1; //進入集合 if (i && dist[t] == INF) return INF; //不存在生成樹 if (i) res += dist[t]; for (int j = 1; j <= n; j ++ ) //更新其它結點 dist[j] = min(dist[j], g[t][j]); } return res; } int main() { cin >> n >> m; memset(g, INF, sizeof g); for (int i = 1; i <= m; i ++ ){ int a, b, v; cin >> a >> b >> v; g[a][b] = g[b][a] = min(g[a][b], v); //無向圖 } int t = prim(); if (t == INF) puts("impossible"); else cout << t << endl; return 0; }
通常用於稀疏圖網絡
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1e5 + 10; const int M = 2 * N; int n,m; int f[N]; //並查集的操做 struct Edge{ int a, b; int v; }edge[M]; bool comp(Edge x, Edge y) //自定義比較 { return x.v < y.v; } int find(int x) //尋找祖宗結點 { if (f[x] != x) return f[x] = find(f[x]); return f[x]; } int main() { cin >> n >> m; for (int i = 1; i <= m; i ++ ){ int a, b, v; cin >> a >> b >> v; edge[i] = {a, b, v}; } sort(edge + 1, edge + m + 1, comp); for (int i = 1; i <= n; i ++ ) //並查集的初始化 f[i] = i; int res = 0; //記錄距離之和 int cnt = 0; //存儲結點數量 for (int i = 1; i <= m; i ++ ){ //枚舉每條邊 int a = edge[i].a; int b = edge[i].b; int v = edge[i].v; int fa = find(a); int fb = find(b); if (fa != fb){ //合併集合 f[fa] = fb; res += v; cnt ++; } } if (cnt < n - 1) puts("impossible"); else cout << res << endl; return 0; }
最小生成樹的模板題, 輸入矩陣形式, 採用prim算法ide
#include <iostream> #include <cstring> using namespace std; const int N = 110; int g[N][N]; int n; int dist[N]; int st[N]; int prim() { memset(dist, 0x3f, sizeof dist); dist[1] = 0; int res = 0; for (int i = 1; i <= n; i ++ ){ int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; st[t] = 1; res += dist[t]; for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]); } return res; } int main() { cin >> n; for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) cin >> g[i][j]; cout << prim() << endl; return 0; }
kruakal算法求最小生成樹, 注意每次當兩個點須要刪邊時, 將結果加上權值.spa
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 110, M = 220; struct Edge{ int a, b, v; bool operator < (const Edge &t)const { return v < t.v; } }edge[M]; int fa[N]; int n ,m; int find(int x) { if (fa[x] != x) fa[x] = find(fa[x]); else return x; } int main() { cin >> n >> m; int res = 0; for (int i = 1; i <= n; i ++ ) fa[i] = i; for (int i = 1; i <= m; i ++ ){ int a, b, v; cin >> a >> b >> v; edge[i] = {a, b, v}; } sort(edge + 1, edge + m + 1); for (int i = 1; i <= m; i ++ ) cout << edge[i].v << endl; for (int i = 1; i <= m; i ++ ){ int a = edge[i].a; int b = edge[i].b; int v = edge[i].v; int aa = find(a); int bb = find(b); if (aa != bb) fa[aa] = bb; else res += v; } cout << res << endl; return 0; }
一樣是模板題, 注意理解題意, 要求被改造的道路可以連通整個圖(被選入生成樹裏的點)排序
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 310, M = 8010; struct Edge{ int a, b, v; bool operator < (const Edge &t)const { return v < t.v; } }edge[M]; int n,m; int fa[N]; int sum; int find(int x) { if (fa[x] != x) fa[x] = find(fa[x]); else return x; } int main() { cin >> n >> m; int res = 0; for (int i = 1; i <= m; i ++ ){ int a, b, v; cin >> a >> b >> v; edge[i] = {a, b, v}; } sort(edge + 1, edge + m + 1); for (int i = 1; i <= n; i ++ ) fa[i] = i; for (int i = 1; i <= m; i ++ ){ int a = edge[i].a; int b = edge[i].b; int v = edge[i].v; int aa = find(a); int bb = find(b); //cout << aa << ' ' << bb << endl; if (aa == bb) continue; else{ sum ++; fa[aa] = bb; res = v; } } cout << sum << ' ' << res << endl; return 0; }
圖中含有必選邊和非必選邊, 對於必選邊咱們直接加入到生成樹中, 而後再根據kruskal算法加入非必選邊ci
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 2010, M = 10010; struct Edge{ int op, a, b, v; bool operator < (const Edge &t)const { if (op == t.op) return v < t.v; return op < t.op; } }edge[M]; int fa[N]; int n, m; int find(int x) { if (fa[x] != x) fa[x] = find(fa[x]); else return x; } int main() { cin >> n >> m; int res = 0; for (int i = 1; i <= n; i ++ ) fa[i] = i; for (int i = 1; i <= m; i ++ ){ int op, a, b, v; cin >> op >> a >> b >> v; edge[i] = {op, a, b, v}; } sort(edge + 1, edge + m + 1); for (int i = 1; i <= m; i ++ ){ int op = edge[i].op; int aa = find(edge[i].a); int bb = find(edge[i].b); int v = edge[i].v; if (op == 1){ res += v; if (aa != bb) fa[aa] = bb; }else{ if (aa == bb) continue; else{ res += v; fa[aa] = bb; } } } cout << res << endl; return 0; }
原圖中有 \(m * n\) 個點, 其中橫向點之間、縱向點之間都有邊, 根據kruskal算法,先加入縱向邊(權值小),再加入橫向邊(權值大).
咱們能夠直接在加邊時進行排序,避免sort().get
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 2 * 1e6 + 10, M = 2 * 1e6 + 10; int g[1010][1010]; struct Edge{ int a, b, v; bool operator < (const Edge &t)const { return v < t.v; } }edge[M]; int fa[N]; int n, m; int find(int x) { if (fa[x] != x) fa[x] = find(fa[x]); return fa[x]; } int main() { cin >> n >> m; for (int i = 1, t = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) g[i][j] = t ++; for (int i = 1; i <= n * m; i ++ ) fa[i] = i; int cnt = 0; for (int i = 1; i <= m; i ++ ) for (int j = 1; j < n; j ++ ){ int a = g[j][i], b = g[j + 1][i]; int v = 1; //cout << a << ' ' << b << endl; edge[cnt ++] = {a, b, v}; } for (int i = 1; i <= n; i ++ ) for (int j = 1; j < m; j ++ ){ int a = g[i][j], b = g[i][j + 1]; int v = 2; //cout << a << ' ' << b << endl; edge[cnt ++] = {a,b,v}; } int x1, y1, x2, y2; while (~scanf("%d%d%d%d",&x1, &y1, &x2, &y2)){ int a = g[x1][y1]; int b = g[x2][y2]; int aa = find(a); int bb = find(b); fa[aa] = bb; } int res = 0; for (int i = 0; i < cnt; i ++ ){ int aa = find(edge[i].a); int bb = find(edge[i].b); int v = edge[i].v; if (aa == bb) continue; else{ res += v; fa[aa] = bb; } } cout << res << endl; return 0; }
圖論中經常使用的假設虛擬原點問題, 假設一個虛擬原點, 到點 \(i\) 的邊的權值爲在該點創建電站\(v_{i}\), 求一個包含全部點的最小生成樹.string
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 310; int g[N][N]; int v[N]; int n; int dist[N]; int st[N]; int prim() { memset(dist, 0x3f, sizeof dist); dist[n + 1] = 0; int res = 0; for (int i = 0; i <= n; i ++ ){ int t = -1; for (int j = 1; j <= n + 1; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; st[t] = 1; if (i) res += dist[t]; for (int j = 1; j <= n + 1; j ++ ) dist[j] = min(dist[j], g[t][j]); } return res; } int main() { cin >> n; for (int i = 1; i <= n; i ++ ) cin >> v[i]; for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) cin >> g[i][j]; for (int i = 1; i < n + 1; i ++ ) g[n + 1][i] = g[i][n + 1] = v[i]; g[n + 1][n + 1] = 0; cout << prim() << endl; return 0; }
圖中有 \(n\) 個點, 有 \(n*n\) 條路徑. 假設起始狀態都不連通, 即有 \(n\) 個連通塊, 根據kruakal算法,每次找到一個邊權值最小的點, 把該點加入到生成樹中, 直到剩餘 \(k\) 個連通塊, 咱們能夠直接用衛星連通.it
#include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; typedef pair<int,int> PII; const int N = 510, M = N * N; int n, k; struct Edge{ int a, b; double v; bool operator < (const Edge &t)const { return v < t.v; } }edge[M]; PII p[N]; int fa[N]; int find(int x) { if (fa[x] != x) fa[x] = find(fa[x]); return fa[x]; } double get_len(PII a, PII b) { double dx = a.first - b.first; double dy = a.second - b.second; return sqrt(dx * dx + dy * dy); } int main() { cin >> n >> k; for (int i = 1; i <= n; i ++ ){ cin >> p[i].first >> p[i].second; fa[i] = i; } int cnt = 0; for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= i; j ++ ){ double v = get_len(p[i], p[j]); edge[cnt ++] = {i,j,v}; } sort(edge, edge + cnt); double ans = 0; int block = n; for (int i = 0; i < cnt; i ++ ){ int aa = find(edge[i].a); int bb = find(edge[i].b); double v = edge[i].v; if (block == k) break; ans = v; if (aa != bb){ block --; fa[aa] = bb; } } printf("%.2f", ans); return 0; }
徹底圖 : 徹底圖是一個簡單的無向圖,其中每對不一樣的頂點之間都恰連有一條邊相連.
題目要求: 知足圖的惟一最小生成樹仍然是原樹, 同時, 要求所加權值之和最小.
每次將兩個連通塊經過已有邊鏈接時, 須要將兩個連通塊中的點都連一條邊, 數量爲 \(size[a] * size[b] - 1\) ,同時知足所加邊權均爲 \(v + 1\) (同時知足權值最小和生成樹惟一).
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 6010; struct Edge{ int a, b, v; bool operator < (const Edge &t)const { return v < t.v; } }e[N]; int fa[N]; int s[N]; int n; int find(int x) { if (x != fa[x]) fa[x] = find(fa[x]); return fa[x]; } int main() { int t; cin >> t; while (t -- ){ cin >> n; for (int i = 1; i < n; i ++ ){ int a, b, v; cin >> a >> b >> v; e[i] = {a, b, v}; } sort(e + 1, e + n); int res = 0; for (int i = 1; i <= n; i ++ ){ fa[i] = i; s[i] = 1; } for (int i = 1; i < n; i ++ ){ int a = find(e[i].a); int b = find(e[i].b); int v = e[i].v; if (a != b){ res += (s[a] * s[b] - 1) * (v + 1); s[b] += s[a]; fa[a] = b; } } cout << res << endl; } return 0; }