初学最小生成树(prim,kruskal //贪心思想)

前言

        1.什么是最小生成树?

                  连通图的所有生成树中边权之和代价最小的生成树  

        2. Prim 算法(找点)

                从已知的某个点每次选择一个距离最小的点,将该点加入集合(跳过已加入的点,否则成环),直至所有点加入集合,构成最小生成树

        3.kruskal算法(找边,并查集辅助)   

                将所有边排序后,从权值最小的一条边开始贪心,不断选择最小的边(只要不构成环)进行点的合并,直至所有点合并,构成最小生成树

        4.两种算法的选择

                稀疏图 --> kruskal;        稠密图 --> prim;

相关知识——最小生成树 - OI Wiki (oi-wiki.org)icon-default.png?t=N7T8https://oi-wiki.org/graph/mst/#%E5%89%8D%E7%BD%AE%E7%9F%A5%E8%AF%86


P3366 【模板】最小生成树

P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3366

 题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

输入格式

第一行包含两个整数N,M,表示该图共有 N 个结点和 M 条无向边。

接下来 M 行每行包含三个整数 Xi​,Yi​,Zi​,表示有一条长度为Zi​ 的无向边连接结点 Xi​,Yi​。

输出格式

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz

输入输出样例

输入

4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3

输出 

7

说明/提示

数据规模:

对于 20% 的数据,N≤5,M≤20。

对于 40% 的数据,N≤50,M≤2500。

对于70% 的数据,N≤500,M≤104。

对于 100% 的数据:1≤N≤5000,1≤M≤2×10^{5},1≤Zi​≤10^{4}

样例解释:

所以最小生成树的总边权为2+2+3=7。


代码示例1(邻接矩阵存图 )

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
using ll = long long;
const int N = 5030;


int ans;
int a[N][N], d[N] ; //邻接矩阵,距离数组
bool intree[N]; //是否在树内


int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;cin >> n >> m;
    memset(a, 0x3f, sizeof a);
    memset(d, 0x3f, sizeof d);
    
    for(int i = 1;  i <= m; i++) a[i][i] = 0;
    
    for(int i = 1; i <= m; i++) {
        int x, y, z;cin >> x >> y >> z;
        a[x][y] = min(z, a[x][y]);
        a[y][x] = min(z, a[y][x]);
    }
    
    for(int i = 1; i <= n; i++) {
        int u = 1; //u即为所找点中距离intree集合中最近的点
        
        for(int j = 1; j <= n; j++) {
            if(intree[u] || (!intree[j] && d[j] < d[u])) u = j; //去除重边即自环
        }
        
        if(u != 1)ans += d[u];
        intree[u] = 1;
        d[u] = 0;
        
        for(int j = 1; j <= n; j++) {
            if(intree[j]) continue;
            d[j] = min(d[j], a[u][j]);
        }
        
    }
    
    
    cout << ans << '\n';
    return 0;
}

由于邻接矩阵存图上限以及朴素的prime算法的复杂的原因,该代码仅能过几个数据比较弱的点。因此,若一定要选择prim算法,我们只能对其进行堆优化----邻接表存图以及优先队列


代码示例2(邻接表存图+优先队列)

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

struct demo{
    ll x, z;
     bool operator < (const demo &u) const {
        return z == u.z ? x < u.x : z > u.z;
     }
};

vector<demo> g[N];
bitset<N> intree;
ll d[N];


int main () {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m; cin >> n >> m;
    
    memset(d, 0x3f, sizeof d);
    
    int x, y, z;
    for(int i = 1; i <= m; i++) {
        cin >> x >> y >> z;
        g[x].push_back({y, z});
        g[y].push_back({x, z});
    }
    
    ll ans = 0;
    
    priority_queue<demo> pq;
    pq.push({1, 0});
    d[1] = 0;
    
    while(pq.size()) {
        int a = pq.top().x; pq.pop();
        if(intree[a]) continue;
        intree[a] = 1;
        
        ans += d[a];
        d[a] = 0;
        
        for(auto &[b, z] : g[a]) {
            if(!intree[b] && z < d[b]) {
                d[b] = z;
                pq.push({b, z});
            }
        }
    }
    cout << ans <<'\n';
    
    return 0;
}

然而本题还需要考虑是否存在最小生成树(是否联通,且样例中有一个要求判断),还需借助并查集判断,然而kruskal直接借助并查集,故不在此进行赘述;


 代码示例3(kruskal)

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

struct demo{
    ll x, y, z;
    bool operator < (const demo &a) const {
        if(z != a.z) return z < a.z;
        if(y != a.y) return y < a.y;
        return x < a.z;
    }
};

int pre[N];

int root(int x) {
    return pre[x] = (pre[x] == x ? x : root(pre[x]));
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m; cin >> n >> m;
    
    vector<demo> g;
    
    ll x, y, z;
    for(int i = 1; i <= m; i++) {
        cin >> x >> y >> z;
        g.push_back({x, y, z});
    }
    
    sort(g.begin(), g.end());
    
    ll ans = 0;
    for(int i = 1; i <= n; i++) pre[i] = i; //并查集初始化
    for(auto &[x, y, z] : g) {
        // 将vector中的所有值取出进行操作
        if(root(x) == root(y)) continue;
        ans += z;
        pre[root(x)] = root(y);
    }
    
    for(int i = 1; i < n; i++) {
        if(root(i) != root(i + 1)) ans = -1;
    } 
     //如果未能联通则说明不存在
     
    if(ans == -1) cout << "orz" << '\n';
    else cout << ans << '\n';
    return 0;
}

 P2121 拆地毯

P2121 拆地毯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P2121

题目描述

会场上有 n 个关键区域,不同的关键区域由 m 条无向地毯彼此连接。每条地毯可由三个整数 u、v、w 表示,其中 u 和 v 为地毯连接的两个关键区域编号,w 为这条地毯的美丽度。

由于颁奖典礼已经结束,铺过的地毯不得不拆除。为了贯彻勤俭节约的原则,组织者被要求只能保留至多 K 条地毯,且保留的地毯构成的图中,任意可互相到达的两点间只能有一种方式互相到达。换言之,组织者要求新图中不能有环。现在组织者求助你,想请你帮忙算出这至多 K 条地毯的美丽度之和最大为多少。

输入格式

第一行包含三个正整数 n、m、K。

接下来 m 行中每行包含三个正整数 u、v、w。

输出格式

只包含一个正整数,表示这 K 条地毯的美丽度之和的最大值。

输入输出样例

输入 

5 4 3
1 2 10
1 3 9
2 3 7
4 5 3

输出 

22

说明/提示

选择第 1、2、4 条地毯,美丽度之和为 10 + 9 + 3 = 22。

若选择第 1、2、3 条地毯,虽然美丽度之和可以达到 10 + 9 + 7 = 26,但这将导致关键区域 1、2、3 构成一个环,这是题目中不允许的。

1<=n,m,k<=100000


题目分析 

         显而易见需借助并查集去除自环,同时与最小生成树相反,本题为最大生成树(重载时注意即可),其次,还需注意k的存在(不然全wa


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

struct demo{
    ll x, y, z;
    bool operator < (const demo &a) const {
        if(z != a.z) return z > a.z;
        if(y != a.y) return y < a.y;
        return x < a.z;
    }
};

int pre[N];

int root(int x) {
    return pre[x] = (pre[x] == x ? x : root(pre[x]));
}


int main() {
    ios::sync_with_stdio(false);
    int n, m, k; cin >> n >> m >> k;
    
    vector<demo> g;
    
    ll u, v, w;
    for(int i = 1; i <= m; i++) {
        cin >> u >> v >> w;
        g.push_back({u, v, w});
    }
    
    sort(g.begin(), g.end());
    
    ll ans = 0, cnt = 0; //计数保留了几张毯子
    for(int i = 1; i <= n; i++) pre[i] = i;
    for(auto &[x, y, z] : g) {
        if(cnt == k) break;
        if(root(x) == root(y)) continue;
        ans += z;
        cnt += 1;
        pre[root(x)] = root(y);
    }
    
    cout << ans << '\n';
    return 0;
}

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值