【模板】最小生成树 · Kruskal + Prim

Kruskal 和 Prim 都可以有负边和负环,
但是 Prim 在稠密图中比 Kruskal 优,在稀疏图中比 Kruskal 劣


Kruskal

#include <bits/stdc++.h>
using namespace std;

inline int read() {
    register int x = 0, t = 1;
    register char ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-')ch = getchar();
    if (ch == '-')t = -1, ch = getchar();
    while (ch <= '9' && ch >= '0')x = x * 10 + ch - 48, ch = getchar();
    return x * t;
}

namespace Kruskal {
    const int MAX_EDGE = 6e3 + 10;
    const int MAX_NODE = 205;

    struct Edge {
        int u, v, w;
        int l, r;
    } e[MAX_EDGE]; // 不需要建双边
    int vis[MAX_NODE], fa[MAX_NODE];

    int find(int x) {
        return fa[x] == x ? x : fa[x] = find(fa[x]);
    }

    void Union(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) return;
        if (x < y) {
            fa[y] = x;
        } else {
            fa[x] = y;
        }
    }

    // m条边插入 最后查询时使用
    int kruskal(int n, int m) { // n个点 m条边
        sort(e + 1, e + 1 + m, [](Edge a, Edge b) { return a.w < b.w; }); // GCC+11及以后的版本才可以使用 否则必须手写cmp

        for (int i = 1; i <= n; i++) vis[i] = 0;
        for (int i = 1; i <= n; i++) fa[i] = i;

        int cost = 0;
        for (int i = 1; i <= m; i++) {
            if (find(e[i].u) == find(e[i].v)) continue;
            Union(e[i].u, e[i].v);
            cost += e[i].w;
        }

        int rt = find(1);
        for (int i = 2; i <= n; i++) {
            if (rt != find(i)) return -1;
        }

        return cost;
    }

    // 每次插入都查询当前是否能构成最小生成树时使用
    int kruskal(int n, int m, Edge edge) { // n个点 已有m条从小到大的边 插入新的边 查询最小生成树
        int k = lower_bound(e + 1, e + m + 1, edge, [](Edge a, Edge b) { return a.w < b.w; }) - e;
        for (int i = m + 1; i > k; i--) {
            e[i] = e[i - 1];
        }
        e[k] = edge;

        int cost = 0;
        for (int i = 1; i <= n; i++) vis[i] = 0;
        for (int i = 1; i <= n; i++) fa[i] = i;

        for (int i = 1; i <= m + 1; i++) {
            if (find(e[i].u) == find(e[i].v)) continue;
            Union(e[i].u, e[i].v);
            cost += e[i].w;
        }

        int rt = find(1);
        for (int i = 2; i <= n; i++) {
            if (rt != find(i)) return -1;
        }
        return cost;
    }
}
using namespace Kruskal;

int n, m, k;
int main() {
    //使用方法:
    n = read();
    m = read();
    for (int i = 1; i <= m; i++) {
        Edge edge = {read(), read(), read()};
        printf("%d\n", kruskal(n, i - 1, edge));
    }
    return 0;
}

Prim

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;

int n, m, K;

namespace Prim { //最小生成树 prim 板子

    //可以有负边 可以有负环
    //返回inf 说明不成图
    const int inf = 0x3f3f3f; //最大值
    
    struct edge {
        int to, next;
        int w;
    } e[N << 1];
    int head[N << 1], tot;

    void add(int u, int v, int w) { //一次建双边
        e[++tot] = {v, head[u], w};
        head[u] = tot;
        e[++tot] = {u, head[v], w};
        head[v] = tot;
    }

    int d[N];
    bool vis[N];
    
    //版本1 链式prim版 可以判断是否连通
    int prim(int n) { //n个点
	    fill(vis, vis + 1 + n, 0);
        for (int i = 2; i <= n; ++i) { //从第二个开始 初始化最大值
            d[i] = inf;
        }
        for (int i = head[1]; i; i = e[i].next) {
            int v = e[i].to;
            d[v] = min(d[v], e[i].w);
        }

        int res = 0;
        int u = 1; //加入的点
        int cnt = 0; //外围for
        while (++cnt < n) { //总共n个点
            int Min = inf;
            vis[u] = 1; //标记已经走过的
            for (int i = 1; i <= n; ++i) { //枚举每一个没有使用过的点
                if (!vis[i] && Min > d[i]) {
                    Min = d[i];
                    u = i;
                }
            }
            if (Min == inf) return inf;//图不连通

            res += Min;
            for (int i = head[u]; i; i = e[i].next) {
                int v = e[i].to;
                if (d[v] > e[i].w && !vis[v]) {
                    d[v] = e[i].w;
                }
            }
        }
        return res;
    }

    //版本2 邻接矩阵prim版 可以判断是否连通 O(n^2)
    const int M = 1e3 + 10;
    int mp[M][M];//建图
    int Prim(int n) {
	    fill(vis, vis + 1 + n, 0);
        for (int i = 2; i <= n; ++i) {
            d[i] = inf;
        }

        int res = 0;
        for (int i = 0; i < n; ++i) {
            int f = -1;
            for (int j = 1; j <= n; ++j) {
                if (!vis[j] && (f == -1 || d[j] < d[f])) {
                    f = j;
                }
            }
            if (i && d[f] == inf) return inf; //不存在不成图的情况
            if (i) res += d[f];
            vis[f] = 1;
            for (int j = 1; j <= n; ++j) {
                if (!vis[j])
                    d[j] = min(d[j], mp[f][j]);
            }
        }
        return res;
    }

    //版本3 堆优化+链式前向星版 O(mlogn)
    typedef pair<int, int> pii;
    priority_queue<pii, vector<pii>, greater<pii> > q;//小根堆
    
    int prim_heap(int n) {
	    fill(vis, vis + 1 + n, 0);
        for (int i = 2; i <= n; ++i) {
            d[i] = inf;
        }
        
        q.push({0, 1});// d v
        int cnt = 0;
        int res = 0;
        while (!q.empty() && cnt < n) {
            int w = q.top().first;
            int u = q.top().second;
            q.pop();
            if (vis[u]) continue;
            cnt++;
            res += w;
            vis[u] = 1;
            for (int i = head[u]; i; i = e[i].next) {
                int v = e[i].to;
                if (d[v] > e[i].w) {
                    d[v] = e[i].w;
                    q.push({d[v], v});
                }
            }
        }

        if (cnt == n) return res;
        else return inf; //不成图
    }
    
	// 多组案例 · 清边+初始化
    void clearE(){
        tot = 0;
        fill(head, head + 1 + n, 0);
    }
    
}
using namespace Prim;

int main() {
    ios::sync_with_stdio(0);

    cin >> n >> m;//n个点 m条边
    for (int i = 1, u, v, w; i <= m; ++i) {
        cin >> u >> v >> w;
        add(u, v, w);
    }
    cout << prim_heap(n) << endl;
    
    return 0;
}

矩阵树定理 - 求生成树的个数

2020 Multi-University Training Contest 6 - Expectation · 矩阵树求生成树个数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值