题目
最小生成树模板题
思路
很明显的最小生成树模板题,求一个图的最小生成树有两种经典的算法: 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;
}