[AcWing.858. Prim算法求最小生成树]

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G=(V, E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|m=|E|

V 中的全部 n 个顶点和 En-1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

输入格式

第一行包含两个整数 nm

接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1 < n < 500,
1 < m < 10^5,
图中涉及边的边权的绝对值均不超过 10000

输入样例:

1
2
3
4
5
6
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

1
6

算法思想

Prim求最小生成树的核心思想:

  1. 从源点开始,将源点纳入最小生成树集合中,求出其余点哪个点离它更近,将更近的点纳入最小生成树集合之中(如果最佳点都不连通,意味着其余点距离都是INF,直接返回INF,因为最小生成树要求每个点都连通)
  2. 在这个过程中,始终是最小生成树集合非最小生成树集合中其余点进行判断。
  3. Prim算法是一种用于求解加权连通无向图的最小生成树的贪心算法

代码实现1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

// 使用邻接矩阵建图,dist是最小生成树集合与点的距离
int g[N][N], dist[N];
bool st[N];
int n, m;

int prim(){
memset(dist, 0x3f, sizeof dist);

// 集合中初始包含点1,距离是0
dist[1] = 0;
// 计算最小生成树的权值(很重要)
int res = 0;

for(int i = 0; i < n; i++){
int t = -1;

for(int j = 1; j <= n; j++){
if(!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
}

// 与 dijkstra不同的是,这里需要判断集合与当前最佳点是否连通
// 如果不连通,那么没有最小生成树
if(dist[t] == INF) return INF;
// 这个点加入最小生成树集合中
st[t] = true;
// 权值累加
res += dist[t];

// 更新最小生成树集合,权值最小的点加入进来,可能会改变其它点的dist值
for(int j = 1; j <= n; j++){
dist[j] = min(dist[j], g[t][j]);
}
}
// 返回最小生成树的权值
return res;
}

int main(){

cin >> n >> m;
memset(g, 0x3f, sizeof g);

while(m --){
int a, b, c;
cin >> a >> b >> c;
// 双向图 + 重边过滤
g[a][b] = g[b][a] = min(g[a][b], c);
}

int res = prim();

if(res == INF) puts("impossible");
else cout << res << endl;

return 0;
}

代码实现2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, M = 200010, INF = 0X3F3F3F3F;

int n, m;
int h[N], e[M], ne[M], w[M], idx;
int dist[N];
bool st[N];

void add(int a, int b, int c){
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}

int prim(){
memset(dist, 0x3f, sizeof dist);

dist[1] = 0;
int res = 0;

for(int i = 0; i < n; i++){
int t = -1;
for(int j = 1; j <= n; j++){
if(!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
}

if(dist[t] == INF) return INF;
st[t] = true;
res += dist[t];

for(int j = h[t]; ~j; j = ne[j]){
int k = e[j];
dist[k] = min(dist[k], w[j]);
}
}

return res;
}

int main(){

memset(h, -1, sizeof h);
cin >> n >> m;

while(m --){
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b, a, c);
}

int res = prim();

if(res > INF / 2) puts("impossible");
else cout << res << endl;

return 0;
}