最小生成树 | P1546 [USACO3.1]最短网络 Agri-Net
题意
给定一个 \(N \times N\) 的矩阵,分别表示 \(1 \sim N\) 号点到其它点的距离,求连接这 \(N\) 个点的最短路径。
分析
将邻接矩阵转换为一个 \(N\) 个点的完全图,题目所求即为该图的最小生成树。
1.最小生成树
对一张图 \(G = (V, E)\),若存在另一张图 \(H = (V', E')\) 满足 \(V' \subseteq V\) 且 \(E' \subseteq E\),则称 \(H\) 是 G 的 子图 (subgraph),记作 \(H \subseteq G\)。
若 \(H \subseteq G\) 满足 \(V' = V\),则称 \(H\) 为 \(G\) 的 生成子图/支撑子图 (spanning subgraph)。
特别地,若 \(G\) 的子图 \(H\) 是一棵树,则称 \(H\) 为 \(G\) 的 生成树 (spanning tree)。
我们定义无向连通图的 最小生成树 (Minimum Spanning Tree,MST) 为边权和最小的生成树。
2.Kruscal 算法
Kruskal 算法是一种常见的最小生成树算法。该算法的基本思想是按边权从小到大加边,本质上是贪心算法。
具体来说,维护一个森林,每次查询两个结点是否在同一棵树中,若不在,则连接两棵树。我们可以通过并查集实现这个算法。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
int n, fa[N], u, v, w, m;
struct edge{ // 维护边的结构体
int u, v, w;
bool operator < (const edge &x){
return w < x.w;
}
}e[N];
inline int xfind(int x){ // 并查集的查询操作
return fa[x] == x ? x : (fa[x] = xfind(fa[x]));
}
inline int kruscal(){ // 求最小生成树
int cnt = 0, ans = 0;
sort(e + 1, e + m + 1); // 对边按权值从小到大排序
for(int i = 1; i <= n; i++) fa[i] = i; // 初始化并查集数组
for(int i = 1; i <= m; i++){
u = xfind(e[i].u), v = xfind(e[i].v);
if(u == v) continue; // 查询 u 与 v 是否在同一集合
ans += e[i].w, fa[u] = v; // 若 u 与 v 不在同一集合,合并
if(++cnt == n - 1) return ans; // 最小生成树只有 n - 1 条边
}
return -1;
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
cin >> w;
if(i < j) e[++m] = {i, j, w}; // 将邻接矩阵转换
}
}
cout << kruscal();
return 0;
}
望穿寂夜晨曦至,雄鹰展翅图九天。

浙公网安备 33010602011771号