1. 程式人生 > 其它 >專題六 最小生成樹

專題六 最小生成樹

技術標籤: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 暢通工程再續

題意: 裸題

題解:

程式碼: