例题
P2330 [SCOI2005] 繁忙的都市
算法简介
Kruskal算法是一种贪心算法,用于在加权连通图中求解最小生成树(MST)。
最小生成树
在给定的连通加权无向图中,最小生成树(MST) 是一棵:
- 包含所有顶点
- 边的权重之和最小
- 无环(即是一棵树) 的子图。
算法思想
- 把所有边按照权值排序(从小到大)
- 每次选取一条边,判断两个顶点是否已经在一个集合内
- 如果没有在一个集合内就合并。
模拟算法过程
有这样一张图:
把边按照权值从小到大排序后结果如下:
- [3 4] 2
- [1 2] 3
- [3 5] 4
- [1 3] 5
- [2 4] 7
- [4 6] 9
- [5 6] 11
边[3 4]
3和4不在一个集合,连接3和4。
边[1 2]
1和2不在一个集合,连接1和2.
边[3 5]
3和5不在一个集合,连接3和5。
边[1 3]
1和3不在一个集合,连接1和3。
边[2 4]
2和4已在一个集合,不操作。
边[4 6]
4和6不在一个集合,连接4和6。
边[5 6]
5和6已在一个集合,不操作。
核心代码如下(通过并查集高效管理顶点的连通性,判断是否成环):
for (int i = 1; i <= m; i++) {
if (Find(a[i].x) != Find(a[i].y)) { // 并查集 找祖先
Merge(a[i].x, a[i].y); // 合并祖先
}
}
例题 P2330 [SCOI2005] 繁忙的都市
提示:最小生成树边数就是n - 1(n为顶点数),不需要统计!
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 305, M = 8005;
struct Node {
int x, y, v;
} a[M];
int n, m, s[N];
void Init() {
for (int i = 1; i <= n; i++)
s[i] = i;
}
int Find(int x) {
return s[x] != x ? s[x] = Find(s[x]) : x;
}
void Merge(int x, int y) {
x = Find(x);
y = Find(y);
if (x != y)
s[x] = y;
}
bool cmp(const Node& a, const Node& b) {
return a.v < b.v;
}
void Kruskal() {
int mx;
for (int i = 1; i <= m; i++) {
if (Find(a[i].x) != Find(a[i].y)) {
Merge(a[i].x, a[i].y);
mx = a[i].v;
}
}
cout << n - 1 << " " << mx << endl;
}
int main() {
cin >> n >> m;
Init();
for (int i = 1; i <= m; i++)
cin >> a[i].x >> a[i].y >> a[i].v;
sort(a + 1, a + 1 + m, cmp);
Kruskal();
return 0;
}