最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可以拥有不止一个城市了!
但是,问题也接踵而来——小Hi现在手上拥有N座城市,且已知这N座城市中任意两座城市之间建造道路所需要的费用,小Hi希望知道,最少花费多少就可以使得任意两座城市都可以通过所建造的道路互相到达(假设有A、B、C三座城市,只需要在AB之间和BC之间建造道路,那么AC之间也是可以通过这两条道路连通的)。
提示:不知道为什么Prim算法和Dijstra算法很像呢Σ(っ °Д °;)っ 。每个测试点(输入文件)有且仅有一组测试数据。
在一组测试数据中:
第1行为1个整数N,表示小Hi拥有的城市数量。
接下来的N行,为一个N*N的矩阵A,描述任意两座城市之间建造道路所需要的费用,其中第i行第j个数为Aij,表示第i座城市和第j座城市之间建造道路所需要的费用。
对于100%的数据,满足N<=10^3,对于任意i,满足Aii=0,对于任意i, j满足Aij=Aji, 0<Aij<10^4.
对于每组测试数据,输出1个整数Ans,表示为了使任意两座城市都可以通过所建造的道路互相到达至少需要的建造费用。
5 0 1005 6963 392 1182 1005 0 1599 4213 1451 6963 1599 0 9780 2789 392 4213 9780 0 5236 1182 1451 2789 5236 0
4178
小Ho也是一路看着小Hi玩这个游戏过来的,于是便提出了自己的想法:“为什么一定要最少花费呢?随便造造能过关不就行了!”
小Hi怒道:“强迫症你知道什么意思么?”
于是小Ho闭嘴不敢言。
小Hi哼了一声,接着道:“也好,趁此机会,我可以教教你最小生成树是什么一回事——先说这个名字的由来,生成树这个名词是相对于一个确定的图G来的,也就是说你不能说一棵树T是生成树,只能说T是G的生成树,而生成树的意思就是,T的结点与G的结点是一样的,而且T的边集是G的边集的子集,这就是所谓的生成树。而最小生成树——意思就是G的所有生成树中边权和最小的一棵。”
“那么最小生成树可以有不止一棵吧?”小Ho问道。
小Hi点头答道:“是的,所以这个问题只需要输出最小生成树的边权和就可以了——这肯定是唯一的。”
小Ho挠了挠头,道:“那到底该怎么做呢?我一点头绪都没有——如果使用枚举的方式那时间复杂度是指数级别的,肯定做不出来!”
“且听我慢慢道来!”小Hi道:“首先我想证明一个结论:对于城市i(i≠1),如果i与城市1的距离不超过其他任何城市j(j≠1)与城市1的距离,那么(1, i)这一条边一定存在于某一棵最小生成树中。”
“你的意思是从城市1出发的最短的边一定属于某棵最小生成树?听上去有点道理,但是为什么啊?”小Ho问道。
小Hi点了点头:“证明是这样的:对于一棵最小生成树T——如果(1, i)在其中,那么该结论就已经得到了证明,所以我们考虑的是(1, i)不在其中的情况。而1和i之间肯定会通过某条路径连通(因为是树不是森林),不妨假设为1-p1-p2-...-pk-i。”
“嗯嗯,然后呢?”
“那么,如果我将(1, p1)这条边删去,添加上(1, i)这条边,那么这还是一棵生成树么?”小Hi问道。
“点没有发生变化,边集仍然是图G的子集,那么只需要看还是不是一个棵树了——所有点之间仍然连通,而且没有环(添加边(1, i)后仅有一个环1-p1-p2-...-pk-i-1,但是(1, p1)却被删去了)!所以这还是一棵树,也就是说……这样改动之后仍然是一棵生成树。”小Ho想了想,说道。
“如果我们称改动后的生成树为T'的话,不难发现,由于(1, p1)的费用大于等于(1, i)的费用,我们可以知道T'的边权和小于等于T,又因为T本身已经是最小生成树了,不难发现T'也会是最小生成树……”小Ho恍然大呼:“也就是说结论得到了证明:(1, i)这一条边一定存在于某一棵最小生成树中。”
“对的!所以万里长征第一步已经走完了,接下来只需要按照第一步走下去就行了!”小Hi道。
“什么意思……接下来的情况要复杂很多吧?”小Ho困惑了。
小Hi叹了口气道:“笨……你这么想,如果我确定了(1, i)这条边一定存在于某一棵最小生成树中,那么我仿照Dijstra算法的思想,将1和i合并为一个点,那么问题是不是就变成了求剩下的N-2个点和这个点的最小生成树?问题没有变化,但是规模却缩小了,那么就只需要一次一次的进行这样的步骤,这个问题不是就能够完美的解决了么?”
小Ho边思索边道:“也就是说……对于一个N个点的最小生成树问题,我求出与1号点最近的点i,将这两个点合并,剩下N-1个点,然后求与新的1号点最近的点i',将这两个点合并,剩下了N-2个点……然后依次类推,直到最后剩下一个点的时候,再将之前所有合并的边的费用加起来——就是答案了!”
“孺子可教也,赶紧去把程序写了!我一会要用呢!”
AC代码:
#include <cstdio> #include <cstring> #include <algorithm> #define INF 0x7f7f7f7f using namespace std; const int MAX = 1005; int map[MAX][MAX], vis[MAX], dis[MAX]; int n; void prim() { for(int i=1; i<=n; i++) dis[i] = map[1][i]; dis[1] = 0; vis[1] = 1; int sum = 0; for(int i=1; i<n; i++) { int tmp = INF, pos; for(int j=1; j<=n; j++) if(!vis[j] && dis[j] < tmp) { tmp = dis[j]; pos = j; } sum += tmp; vis[pos] = 1; for(int j=1; j<=n; j++) if(!vis[j] && map[pos][j] < dis[j]) dis[j] = map[pos][j]; } printf("%d\n", sum); } int main() { while(scanf("%d", &n) != EOF) { memset(vis, 0, sizeof(vis)); memset(map, 0x7f, sizeof(map)); for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) scanf("%d", &map[i][j]); prim(); } return 0; }