專題六 最小生成樹
技術標籤:ACM--比賽補題
文章目錄
專題六 最小生成樹
POJ 1251 Jungle Roads
題意: 給一張圖,求最小生成樹
題解: 模板提
程式碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
typedef long long LL;
int const MAXN = 40;
int n, m, T, tot;
map<string, int> mp;
int g[MAXN][MAXN], dis[MAXN], st[MAXN];
// prime演算法
int prime()
{
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 記錄最小生成樹的距離和
for (int i = 0; i < n; ++i) // 外迴圈n次
{
int t = -1; // 記錄到集合最小的點
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合內有點且t點到集合的距離為無窮(即表示t不在連通圖內), 不存在最小生成樹
if (i) res += dis[t]; // 如果集合內有點,累加res
for (int j = 1; j <= n; ++j) // 更新所有與t連線的點
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
while(cin >> n){
if (!n) break;
memset(g, 0x3f, sizeof g);
mp.clear();
tot = 0;
for (int i = 0, t; i < n - 1; ++i) {
string s1, s;
cin >> s1 >> t;
if (!mp.count(s1)) mp[s1] = ++tot;
for (int j = 0, tt; j < t; ++j) {
cin >> s >> tt;
if (!mp.count(s)) mp[s] = ++tot;
g[mp[s1]][mp[s]] = g[mp[s]][mp[s1]] = min(g[mp[s]][mp[s1]], tt);
}
}
cout << prime() << endl;
}
return 0;
}
POJ 1287 Networking
題意: 給定n個點m條邊,求最小生成樹。
題解: 裸題
程式碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
int const N = 5e2 + 10, M = 1e5 + 10;
int g[N][N], dis[N], st[N];
int n, m;
// prime演算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 記錄最小生成樹的距離和
for (int i = 0; i < n; ++i) {// 外迴圈n次
int t = -1; // 記錄到集合最小的點
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合內有點且t點到集合的距離為無窮(即表示t不在連通圖內), 不存在最小生成樹
if (i) res += dis[t]; // 如果集合內有點,累加res
for (int j = 1; j <= n; ++j) // 更新所有與t連線的點
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
if (!n) break;
memset(g, 0x3f, sizeof g); // 初始化鄰接矩陣
for (int i = 0; i < m; ++i) {// 讀入邊資訊
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c); // 無向圖注意要賦值兩次
}
cout << prime() << endl;
}
return 0;
}
POJ 2031 Building a Space Station
題意: 空間有很多球…
題解: 求任意2個球圓心距離,如果距離大於兩個半徑和,那麼就是距離-半徑和,要不然就是0,然後跑最小生成樹
程式碼: 程式碼有點不想寫了,貼上其他人的:https://blog.csdn.net/In_Youth/article/details/47080187?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <climits>
using namespace std;
double maz[109][109];
double d[109];
bool vis[109];
double dis(double a[], double b[])
{
double x, y, z, sum;
x = (a[0] - b[0]) * (a[0] - b[0]);
y = (a[1] - b[1]) * (a[1] - b[1]);
z = (a[2] - b[2]) * (a[2] - b[2]);
sum = sqrt((x + y + z) * 1.0);
sum = sum - (a[3] + b[3]);
return sum;
} //得到兩點之間的距離!
void prim(int n)
{
int i, j, pos;
double sum, t;
for (i = 1; i <= n; ++i)
d[i] = maz[1][i];
memset(vis, false, sizeof(vis));
vis[1] = true;
sum = 0;
for (i = 0; i < n - 1; ++i)
{
t = INT_MAX;
for (j = 1; j <= n; ++j)
{
if (!vis[j] && d[j] < t)
{
t = d[j];
pos = j;
}
}
sum += t;
vis[pos] = true;
for (j = 1; j <= n; ++j)
{
if (!vis[j] && d[j] > maz[pos][j])
{
d[j] = maz[pos][j];
}
}
}
printf("%.3lf\n", sum);
}
int main()
{
int t, i, j;
double data[109][9], tem;
while (~scanf("%d", &t) && t)
{
for (i = 1; i <= t; ++i)
{
for (j = 1; j <= t; ++j)
maz[i][j] = i == j ? 0 : INT_MAX;
} //初始化maz陣列
for (i = 1; i <= t; ++i)
{
for (j = 0; j < 4; ++j)
scanf("%lf", &data[i][j]);
} //儲存原始資料
for (i = 1; i <= t; ++i)
{
for (j = i + 1; j <= t; ++j)
{
tem = dis(data[i], data[j]);
if (tem <= 0)
maz[i][j] = maz[j][i] = 0;
else
maz[i][j] = maz[j][i] = tem;
}
} //用兩層for迴圈計算任意兩點之間的距離,將最後結果儲存在maz數組裡
prim(t); //模板prim演算法
}
return 0;
}
POJ 2421 Constructing Roads
題意: 輸入一個n * n的矩陣,表示任意2個點的距離。然後給定q對數字a和b,表示a和b之間已經連通。現在要修公路,使得任意2個點聯通,問最少要修多長的公路。
題解: 對於q對數字,記他們間的距離為0。然後跑最小生成樹即可。
程式碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
int const N = 5e2 + 10, M = 1e5 + 10;
int g[N][N], dis[N], st[N];
int n, m;
// prime演算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 記錄最小生成樹的距離和
for (int i = 0; i < n; ++i) {// 外迴圈n次
int t = -1; // 記錄到集合最小的點
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合內有點且t點到集合的距離為無窮(即表示t不在連通圖內), 不存在最小生成樹
if (i) res += dis[t]; // 如果集合內有點,累加res
for (int j = 1; j <= n; ++j) // 更新所有與t連線的點
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int main() {
freopen("in.txt", "r", stdin);
while (scanf("%d%d", &n, &m) != EOF) {
if (!n) break;
memset(g, 0x3f, sizeof g); // 初始化鄰接矩陣
for (int i = 0; i < m; ++i) {// 讀入邊資訊
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c); // 無向圖注意要賦值兩次
}
cout << prime() << endl;
}
return 0;
}
ZOJ 1586 QS Network
題意: 最小生成樹裸題
題解: 最小生成樹裸題
程式碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
typedef long long LL;
using namespace std;
int const N = 1010, M = N * 2;
int g[N][N], dis[N], st[N], cost[N];
int n, m;
// prime演算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 記錄最小生成樹的距離和
for (int i = 0; i < n; ++i) {// 外迴圈n次
int t = -1; // 記錄到集合最小的點
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合內有點且t點到集合的距離為無窮(即表示t不在連通圖內), 不存在最小生成樹
if (i) res += dis[t]; // 如果集合內有點,累加res
for (int j = 1; j <= n; ++j) // 更新所有與t連線的點
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int T;
int main() {
cin >> T;
while (T--) {
cin >> n;
memset(g, 0x3f, sizeof g); // 初始化鄰接矩陣
for (int i = 1, t; i <= n; ++i) cin >> cost[i];
for (int i = 1; i <= n; ++i) {
for (int j = 1, t; j <= n; ++j) {
cin >> t;
g[i][j] = t + cost[i] + cost[j];
}
}
cout << prime();
if (T) cout << endl;
}
return 0;
}
POJ 1789 Truck History
題意: 給定n個字串,每個字串看作一個點,2個點間的距離為不同給定字元個數,球最小生成樹。
題解: 裸題
程式碼:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <climits>
#include <string>
using namespace std;
int const N = 2e3 + 10, M = 1e5 + 10;
int g[N][N], dis[N], st[N];
int n, m;
string s[N];
// prime演算法
int prime() {
memset(dis, 0x3f, sizeof dis); // dis初始化
memset(st, 0, sizeof st);
int res = 0; // 記錄最小生成樹的距離和
for (int i = 0; i < n; ++i) {// 外迴圈n次
int t = -1; // 記錄到集合最小的點
for (int j = 1; j <= n; ++j)
if (!st[j] && (t == -1 || dis[j] < dis[t]))
t = j; // 找到t
if (i && dis[t] == 0x3f3f3f3f) return dis[t]; // 如果集合內有點且t點到集合的距離為無窮(即表示t不在連通圖內), 不存在最小生成樹
if (i) res += dis[t]; // 如果集合內有點,累加res
for (int j = 1; j <= n; ++j) // 更新所有與t連線的點
dis[j] = min(dis[j], g[t][j]);
st[t] = 1; // 把t放入集合
}
return res; // 返回res
}
int cal(int idx1, int idx2) {
int res = 0;
for (int i = 0; i < 7; ++i) res += (s[idx1][i] != s[idx2][i]);
return res;
}
int main() {
while(scanf("%d", &n) != EOF) {
if (!n) break;
memset(g, 0x3f, sizeof g); // 初始化鄰接矩陣
for (int i = 1; i <= n; ++i) cin >> s[i];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
g[i][j] = g[j][i] = cal(i, j);
}
}
printf("The highest possible quality is 1/%d.\n", prime());
}
return 0;
}
POJ 2349 Arctic Network
題意: 北極的某區域共有 n 座村莊,每座村莊的座標用一對整數 (x,y) 表示。為了加強聯絡,決定在村莊之間建立通訊網路,使每兩座村莊之間都可以直接或間接通訊。通訊工具可以是無線電收發機,也可以是衛星裝置。無線電收發機有多種不同型號,不同型號的無線電收發機有一個不同的引數 d,兩座村莊之間的距離如果不超過 d,就可以用該型號的無線電收發機直接通訊,d 值越大的型號價格越貴。現在要先選擇某一種型號的無線電收發機,然後統一給所有村莊配備,數量不限,但型號都是相同的。配備衛星裝置的兩座村莊無論相距多遠都可以直接通訊,但衛星裝置是有限的,只能給一部分村莊配備。現在有 k 臺衛星裝置,請你編一個程式,計算出應該如何分配這 k 臺衛星裝置,才能使所配備的無線電收發機的 d 值最小。
題解: 把本題翻譯過來就是求給定一個d,刪去圖中權值大於d的所有邊,做最小生成樹,得到的連通塊數目要求小於等於k,求這麼個d
通常的思路是二分d,然後求連通塊個數,判斷連通塊個數和k的關係
但是kruskal具有單調性,列舉的邊從小到大,集合的數目從大到小,具有單調性,因此不需要二分,只需要每次列舉完判斷一下當前的集合個數是否等於k,如果等於k,那麼說明當前放入的這條邊的權值就是d。
程式碼: poj一直沒編譯過,放在其他平臺交的
#include<bits/stdc++.h>
using namespace std;
typedef pair<double, double> PDD;
unordered_map<int, PDD> grid;
int n, k, cnt, m;
int const N = 5e2 + 10, M = N * N;
int fa[N];
struct Edge {
int a, b;
double w;
bool operator< (const Edge &W) {
return w < W.w;
}
}edge[M];
int get(int x) {
if (x == fa[x]) return x;
return fa[x] = get(fa[x]);
}
void kruskal() {
for (int i = 1; i <= cnt; ++i) fa[i] = i;
int cnt_con = cnt; // 記錄連通塊個數
if (cnt_con < k) {
cout << 0.00 << endl;
return;
}
// 從小到大列舉邊
sort(edge, edge + m);
for (int i = 0; i < m; ++i) {
int a = edge[i].a, b = edge[i].b;
double w = edge[i].w;
int pa = get(a), pb = get(b);
if (pa != pb) {
fa[pa] = pb;
cnt_con--; // 每次加入邊後,如果兩個端點不在一個連通塊內,那麼合併後連通塊個數將會減一
}
if (cnt_con == k) { // 一旦減到k,說明當前的邊權值即為d
printf("%.2lf", w);
return;
}
}
}
int main() {
cin >> n >> k;
// 給每個節點標號
for (int i = 0; i < n; ++i) {
int x, y;
cin >> x >> y;
grid[++cnt] = {x, y};
}
// 計算每個節點間的距離
for (int i = 1; i <= cnt; ++i)
for (int j = 1; j <= cnt; ++j) {
int dx = grid[i].first - grid[j].first, dy = grid[i].second - grid[j].second;
edge[m++] = {i, j, sqrt(dx * dx + dy * dy)};
}
// 做最小生成樹
kruskal();
return 0;
}
POJ 1751 Highways
題意: 給定n個村莊的座標,需要使得這n個村莊聯通。給定m條路,表示a村莊和b村莊聯通,不需要修路。現在問要把所有村莊聯通需要的最少花費為多少。
題解: 歐氏距離計算花費,對於已經修過的,那麼花費為0.
程式碼: 和前面一個題目基本一樣,沒啥意思,不寫了,貼上大佬的程式碼:https://www.cnblogs.com/alihenaixiao/p/4547542.html
#include <cmath>
#include <string>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 760;
const int INF = 0x3f3f3f3f;
const double Exp = 1e-10;
int cost[maxn][maxn], lowc[maxn];
int vis[maxn], pre[maxn];
//pre[i]記錄i的前驅
struct point
{
int x, y;
};
int length (point a, point b)
{
return (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
}
void prim (int n)
{
int i, j;
memset (vis, 0, sizeof(vis));
vis[1] = 1;
for (i=1; i<=n; i++)
{
pre[i] = 1;
lowc[i] = cost[1][i];
}
for (i=1; i<n; i++)
{
int p;
int mini = INF;
for (j=1; j<=n; j++)
if (!vis[j] && mini > lowc[j])
{
p = j;
mini = lowc[j];
}
if (cost[p][pre[p]] != 0)//需要建路
printf ("%d %d\n", pre[p], p);
vis[p] = 1;
for (j=1; j<=n; j++)
{
if (!vis[j] && lowc[j] > cost[p][j])
{
lowc[j] = cost[p][j];
pre[j] = p;
}
}
}
}
int main ()
{
int n;
point p[maxn];
scanf ("%d", &n);
for (int i=1; i<=n; i++)
{
scanf ("%d %d", &p[i].x, &p[i].y);
for (int j=1; j<i; j++)
cost[i][j] = cost[j][i] = length(p[i], p[j]);
cost[i][i] = 0;
}
int m;
scanf ("%d", &m);
while (m --)
{//把已經建過路的點聚集在一起
int x, y;
scanf ("%d %d", &x, &y);
cost[x][y] = cost[y][x] = 0;
}
prim(n);
return 0;
}
POJ 1258 Agri-Net
題意: 完全裸題,直接看別人的程式碼把:https://blog.csdn.net/versionwen/article/details/99686746
題解:
程式碼:
POJ 3026 Borg Maze
題意: 直接看別人題解吧,比較裸題:https://blog.csdn.net/qq_40421671/article/details/83041978
題解:
程式碼:
POJ 1679 The Unique MST
題意: 判斷一個最小生成樹是否唯一
題解: 先預處理一遍,求出最小生成樹上的邊,然後暴力列舉這些邊,刪除之後再跑一遍最小生成樹,看是否和原來一樣。
程式碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;
typedef long long LL;
LL n, m;
LL const N = 1000 + 10;
struct E {
LL u, v;
LL w;
}edge[N*N];
bool cmp(struct E a, struct E b) {
return a.w < b.w;
}
LL p[N*N], st[N*N];
vector<LL> used;
LL find(LL x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
// kruskal演算法
LL kruskal(LL cn) {
LL res = 0;
LL cnt = 0; // res記錄最小生成樹的權值,cnt記錄當前最小生成樹內有幾條邊
for (LL i = 1; i <= n; ++i) p[i] = i; // 並查集初始化
for (LL i = 0; i < m ; ++i) {// 列舉每一條邊
if (cn==i) continue;
LL a = edge[i].u, b = edge[i].v;
LL c = edge[i].w; // 邊a, b, 權值為c
a = find(a), b = find(b); // 得到a的父節點和b的父節點
if (a != b) {// 判斷a和b是否在同一個集合內
res += c; // 在的話放入集合內
if (cn == -1) used.push_back(i);
cnt++;
p[a] = b;
}
}
if (cnt < n-1) return -1;
else return res;
}
LL T;
int main() {
cin >> T;
while(T--) {
used.clear();
cin >> n >> m;
for (LL i = 0, a, b, c; i < m; ++i) {
scanf("%lld%lld%lld", &edge[i].u, &edge[i].v, &edge[i].w);
/* E tmp;
tmp.u = a, tmp.v = b, tmp.w = c;
edge[i] = tmp;
*/
}
sort(edge , edge + m, cmp);
LL mst = kruskal(-1); // 最早的最小生成樹
LL flg = 0;
for (LL i = 0; i < used.size(); ++i) { // 暴力列舉刪除的邊
LL idx = used[i]; // 邊的下標
LL new_mst = kruskal(idx);
if (new_mst == mst) {
flg = 1;
break;
}
}
if (flg) cout << "Not Unique!\n";
else cout << mst << endl;
}
return 0;
}
HDU 1233 還是暢通工程
題意: 裸題
題解:
程式碼:
HDU 1301 Jungle Roads
題意: 裸題
題解:
程式碼:
HDU 1875 暢通工程再續
題意: 裸題
題解:
程式碼: