最小生成树 | 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;
}
posted @ 2023-06-25 15:44  心灵震荡  阅读(30)  评论(0)    收藏  举报