前言
1.什么是最小生成树?
连通图的所有生成树中边权之和代价最小的生成树
2. Prim 算法(找点)
从已知的某个点每次选择一个距离最小的点,将该点加入集合(跳过已加入的点,否则成环),直至所有点加入集合,构成最小生成树
3.kruskal算法(找边,并查集辅助)
将所有边排序后,从权值最小的一条边开始贪心,不断选择最小的边(只要不构成环)进行点的合并,直至所有点合并,构成最小生成树
4.两种算法的选择
稀疏图 --> kruskal; 稠密图 --> prim;
P3366 【模板】最小生成树
P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://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×,1≤Zi≤。
样例解释:
所以最小生成树的总边权为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)https://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;
}