【代码超详解】HDU 1233 还是畅通工程(Prim算法求最小生成树 · 模板题,156 ms)

一、题目描述

还是畅通工程

Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 71626 Accepted Submission(s): 32388(2019/11/3)

Problem Description

某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。

Input

测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。

Output

对每个测试用例,在1行里输出最小的公路总长度。

Sample Input

3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0

Sample Output

3
5

Hint

Hint

Huge input, scanf is recommended.

Source

浙大计算机研究生复试上机考试-2006年

Recommend

JGShining | We have carefully selected several similar problems for you: 1102 1875 1879 1162 1863

二、算法分析说明与代码编写指导

Prim算法
1、算法用途:求图的最小生成树。
2、算法内容:
设图G(V, E)。设集合X是已经找到生成树的顶点集(显然X是V的子集)。重复以下步骤,直至X = V:
考察连接V中任一顶点和V-X中任一顶点的边,选出权最小的边添加到生成树中。
这样的算法构造的生成树T确为最小生成树。

证明:反证法。设Prim算法的任意一次循环中找到了连接集合X和V-X的权最小的边e。如果构造出的T不是最小生成树S,那么不妨就设
e不在S中。由于S是最小生成树,因此将e添加到S上,就形成了环。显然,这个环至少会存在一条与e不同的边f,且f也是连接V和V-X的。
对这个加入了e的新图,分别删掉e和删掉f就构成了T和S。然而,S和T的所有边权之和的差就是weight(f)weight(e)>0,因为
f不是同一次循环中连接已构造生成树的顶点集X和未构造生成树的顶点集V-X的权最小的边。这与S是最小生成树矛盾,所以T才是最小生成树,
Prim算法是正确的。

一种实现方法是:用额外的数组d保存最后添加到集合X中的点到其它各点的边权。先设法令顶点0(其它点也可以)将会最先被添加到
集合X中(记d[0] = 0)。循环下列步骤,直到考察完全部顶点:
考察最后添加到X中的点到其它点的权。选出令权最小的点,标记已访问。最后添加的点到这个使直连边权最小的点的边,就是权最小的边。
将该点也添加到X中。

第一次循环中,会首先选出顶点0(或指定的其它第一个添加的顶点),然后更新0到其它点的权。在下一次循环中,选中并添加的就是
上一次循环中添加的点到剩余点的权最小的点了。
这样选下来,每次选到的生成树的边都是到剩余的顶点的权最小的边。可见,Prim算法属于贪心的衍生算法。

另:如果进入循环前先初始化d数组,令未被选中最先放入的顶点的d值正好为该点到被选入的顶点的权值,则循环|V|1次,循环到考察完
倒数第二个顶点即可。
至于为什么只循环到倒数第二个顶点,因为在考察倒数第二个点到剩余的未添加到生成树中的点的所有边的权的时候,已经只剩1条边了
(正好是倒数第二个顶点到最后一个顶点),生成树的最后一条边只能选这一条。先更新使权最小的点,再更新该点到剩余点的权最小的边。
这在某种程度上有点像Floyd算法求无向图最小环。因为该实现方法与Floyd求无向图的最小环存在一个共同点:最后一个循环中,后做的更新
并没有什么屁用。还有一种理解更简单,就是:|V|1次循环正好找到最小生成树的|V|1条边。而不按照该方法先处理d数组,
则需要额外的一次循环先将指定点选入生成树,再开始更新添加到X中的点到其它点的权。

3、算法性质:
一个图是否存在最小生成树和这个图是否连通是等价的。如果最小生成树构造失败,那么该图不是连通图。

本题的图为完全图,因此应该直接采用邻接矩阵存图并处理,而不要采用邻接表存图 + 堆优化处理,否则复杂度会过高,可能导致超时。

三、AC 代码(156 ms)

#include<cstdio>
#include<algorithm>
#include<bitset>
#pragma warning(disable:4996)
using namespace std;
const unsigned M = 1 << 30;
unsigned G[101][101], d[101], N, E, u, v, w, V, A; bitset<101> visited;
int main() {
	for (;;) {
		scanf("%u", &N); if (N == 0)return 0;
		E = N * (N - 1) >> 1; visited.reset(); A = 0;
		for (unsigned i = 1; i <= N; ++i)fill(G[i] + 1, G[i] + N + 1, M);
		for (unsigned i = 0; i < E; ++i)scanf("%u%u%u", &u, &v, &w), G[u][v] = G[v][u] = w;
		fill(d, d + N + 1, M); d[1] = 0; visited[1] = 1;
		for (unsigned i = 1; i < N; ++i) {
			V = 0;
			for (unsigned j = 1; j <= N; ++j) {
				if (visited[j] == 0 && d[j] < d[V])V = j;
			}
			visited[V] = 1; A += d[V];
			for (unsigned j = 1; j <= N; ++j)d[j] = min(d[j], G[V][j]);
		}
		printf("%u\n", A);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值