【NC15108】道路建设

题目

道路建设

最小生成树模板题

思路

很明显的最小生成树模板题,求一个图的最小生成树有两种经典的算法: P r i m Prim Prim 算法和 K r u s k a l Kruskal Kruskal 算法,两种算法都是基于贪心的思想,算法细节不再赘述,这里直接给出模板。

代码

P r i m Prim Prim 算法,使用优先队列优化

#include <climits>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

/**
 * @brief Prim 模板
 *
 */
class Prim {
    using PII = pair<int, int>;
    // 注意这里的 pair<int,int> 表示的是 <边权,终点>
    vector<vector<PII>> adj;  // 邻接表存图
    vector<bool> vis;         // 标记数组
    // 按边权从小到大排序的优先队列(小顶堆)
    priority_queue<PII, vector<PII>, greater<>> pq;
    int n;  // 结点个数

   public:
    /**
     * @brief 根据结点个数和邻接矩阵构造对象
     *
     * @param mat n*n 的邻接矩阵
     */
    Prim(vector<vector<int>> &&mat) : n(mat.size()) {
        adj.resize(n);
        vis.resize(n, false);
        // 根据邻接矩阵初始化邻接表
        int i = 0, j = 0;
        for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++) {
                if (mat[i][j] == INT_MAX) continue;
                adj[i].emplace_back(mat[i][j], j);
            }
        }
    }

    /**
     * @brief 从 start 结点开始 Prim 算法
     *
     * @param start [0, n-1]
     * @return int 最小生成树总代价
     */
    int prim(int start) {
        pq.emplace(0, start);
        PII top;
        int res = 0;
        while (!pq.empty()) {
            top = pq.top();
            pq.pop();
            if (vis[top.second]) continue;
            res += top.first;
            vis[top.second] = true;
            for (auto &&e : adj[top.second]) {
                if (vis[e.second]) continue;
                pq.emplace(e);
            }
        }
        return res;
    }

    ~Prim() {}
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int c = 0, m = 0, n = 0;
    int a = 0, b = 0, v = 0;
    while (cin >> c >> m >> n) {
        vector<vector<int>> edges(n, vector<int>(n, INT_MAX));
        while (m--) {
            cin >> a >> b >> v;
            edges[b - 1][a - 1] = edges[a - 1][b - 1] =
                min(edges[a - 1][b - 1], v);
        }
        Prim *p = new Prim(std::move(edges));
        v = p->prim(0);
        delete p;
        cout << (v <= c ? "Yes" : "No") << endl;
    }
    return 0;
}

K r u s k a l Kruskal Kruskal 算法,使用并查集判环(在不在一个连通块)

#include <algorithm>
#include <climits>
#include <iostream>
#include <numeric>
#include <queue>
#include <vector>

using namespace std;

class Kruskal {
    vector<vector<int>> edges;  // 边集
    int n;                      // 结点个数
    vector<int> fa;             // 并查集

    // 初始化并查集
    void _init() {
        fa.resize(n);
        std::iota(fa.begin(), fa.end(), 0);
    }

    // 查,在查的过程中降低树的高度,这样就不用在
    // 并的时候降低树的高度(写法更复杂)
    int _find(int x) {
        int r = x;
        while (r ^ fa[r]) {
            r = fa[r];
        }
        int i = x, j = 0;
        while (i ^ r) {
            j = fa[i];
            fa[i] = r;
            i = j;
        }
        return r;
    }

    // 并,如果想追求极致效率的话,在并的时候也要写降低树的操作
    // 但是一般来说只在一个操作里写就行了(上面已经写了)
    // 注意此题有些许变化,这里的写法并不是一般并查集的写法,
    bool _union(int x, int y) {
        x = _find(x);
        y = _find(y);
        if (x ^ y) {
            fa[x] = y;
            return true;
        }
        return false;
    }

   public:
    /**
     * @brief 根据结点个数和邻接矩阵初始化
     *
     * @param mat n*n 邻接矩阵
     */
    Kruskal(vector<vector<int>>&& mat) : n(mat.size()) {
        // 初始化并查集
        _init();
        // 初始化边集
        edges.clear();
        int i = 0, j = 0;
        for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++) {
                if (mat[i][j] == INT_MAX) continue;
                edges.emplace_back(vector<int>{mat[i][j], i, j});
            }
        }
        // 边集按权值从小到大排序
        sort(edges.begin(), edges.end(),
             [&](const vector<int>& a, const vector<int>& b) {
                 return a[0] < b[0];
             });
    }

    /**
     * @brief Kruskal算法实现
     *
     * @return int
     */
    int kruskal() {
        // 注意这里可以优化:即边添加到 n-1 条时就可以退出
        return accumulate(edges.begin(), edges.end(), 0,
                          [&](int res, const vector<int>& a) {
                              return res + (_union(a[1], a[2]) ? a[0] : 0);
                          });
    }

    ~Kruskal() {}
};

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int c = 0, m = 0, n = 0;
    int a = 0, b = 0, v = 0;
    while (cin >> c >> m >> n) {
        vector<vector<int>> edges(n, vector<int>(n, INT_MAX));
        while (m--) {
            cin >> a >> b >> v;
            edges[b - 1][a - 1] = edges[a - 1][b - 1] =
                min(edges[a - 1][b - 1], v);
        }
        Kruskal* k = new Kruskal(std::move(edges));
        v = k->kruskal();
        delete k;
        cout << (v <= c ? "Yes" : "No") << endl;
    }
    return 0;
}
  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木又可可

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值