专题六 最小生成树

这个专题详细讲解了最小生成树的相关问题,包括多个在线编程比赛中的实例,如POJ 1251, POJ 1287等。通过解决这些题目,展示了如何运用最小生成树算法解决实际问题,如构建网络、道路建设等。涉及到的解题策略包括经典的Prim和Kruskal算法,以及特殊情况的处理和优化。" 82425991,7916642,深入理解JSP基础与核心概念,"['java', 'jsp', 'web开发', '服务器', '基础教程']
摘要由CSDN通过智能技术生成

专题六 最小生成树

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 畅通工程再续

题意: 裸题

题解:

代码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值