使用 Kruskal 算法按边权从小到大选不成环的边,并用并查集维护连通块。
OJ: luogu
题目 ID: P3366
难度:普及/提高-
标签:模板最小生成树Kruskal并查集图论
日期: 2026-01-03 09:38
题意
给定一个无向带权图,求最小生成树权值和。
如果图不连通,输出 orz。
思路
先看一个可以直接验证想法的朴素解:
cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10;
const int MAXM = 25;
struct Edge {
int u;
int v;
int w;
};
int n, m;
Edge edges[MAXM];
int fa[MAXN];
int find_set(int x) {
if (fa[x] == x) {
return x;
}
fa[x] = find_set(fa[x]);
return fa[x];
}
bool check_mask(int mask, long long &sum) {
int cnt = 0;
sum = 0;
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
for (int i = 0; i < m; i++) {
if ((mask & (1 << i)) == 0) {
continue;
}
cnt++;
sum += edges[i + 1].w;
int fu = find_set(edges[i + 1].u);
int fv = find_set(edges[i + 1].v);
if (fu == fv) {
return false;
}
fa[fu] = fv;
}
if (cnt != n - 1) {
return false;
}
int root = find_set(1);
for (int i = 2; i <= n; i++) {
if (find_set(i) != root) {
return false;
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> edges[i].u >> edges[i].v >> edges[i].w;
}
long long ans = -1;
for (int mask = 0; mask < (1 << m); mask++) {
long long sum;
if (check_mask(mask, sum)) {
if (ans == -1 || sum < ans) {
ans = sum;
}
}
}
if (ans == -1) {
cout << "orz\n";
} else {
cout << ans << '\n';
}
return 0;
}正式做法使用 Kruskal。
把所有边按权值从小到大排序。依次考虑每条边:如果这条边连接的是两个不同连通块,就把它加入生成树;否则加入它会成环,跳过。
连通块用并查集维护。
最后如果选出的边数是 n-1,说明得到了最小生成树;否则图不连通。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int MAXM = 200005;
struct Edge {
int u;
int v;
int w;
};
int n, m;
Edge edges[MAXM];
int fa[MAXN], sz[MAXN];
bool cmp_edge(const Edge &a, const Edge &b) {
return a.w < b.w;
}
void init_dsu() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
sz[i] = 1;
}
}
int find_set(int x) {
while (fa[x] != x) {
fa[x] = fa[fa[x]];
x = fa[x];
}
return x;
}
bool unite_set(int x, int y) {
int fx = find_set(x);
int fy = find_set(y);
if (fx == fy) {
return false;
}
if (sz[fx] < sz[fy]) {
swap(fx, fy);
}
fa[fy] = fx;
sz[fx] += sz[fy];
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> edges[i].u >> edges[i].v >> edges[i].w;
}
sort(edges + 1, edges + m + 1, cmp_edge);
init_dsu();
long long ans = 0;
int cnt = 0;
for (int i = 1; i <= m; i++) {
if (unite_set(edges[i].u, edges[i].v)) {
ans += edges[i].w;
cnt++;
if (cnt == n - 1) {
break;
}
}
}
if (cnt != n - 1) {
cout << "orz\n";
} else {
cout << ans << '\n';
}
return 0;
}复杂度
排序复杂度为:
text
O(m log m)并查集操作近似线性,空间复杂度为
总结
Kruskal 的核心是:每次选择当前最小的、连接两个不同连通块的边。
并查集负责快速判断一条边是否会成环。
