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 · 矩阵树求生成树个数