GDUT_寒假训练题解报告_图论专题_个人题解报告——题目:I - 还是畅通工程
(更新:我用了Floyd+贪心,然而无意之中做出来了prim算法orz,无心插柳柳成荫)
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
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
读题完了之后,Floyd算法和dijkstra算法板子写过几遍的我先想到的是floyd+贪心,然后乱搞一下就搞出来,这个floyd板子不用怎么说,看代码:
for(int time=0;time<n;time++)
{
for(int time1=0;time1<n;time1++)
{
for(int time2=0;time2<n;time2++)
{
edge[time1][time2]=min(edge[time1][time2],edge[time1][time]+edge[time][time2]);
edge[time2][time1]=edge[time1][time2];
}
}
}
主要是贪心这里的思路:我做了什么呢,有一点点dijkstra内味儿,因为dijkstra也是贪心搞出来的bellman-ford剪枝,他的思想是最短边,那我们可以看到这个图很多边,随便找一个点,我找了0点,并且把它used掉
fill(used,used+n,false);
used[0]=true;
这个地方,我used掉了0,之后进入一个函数:
void dfs()
{
int _MIN=INF;
int point1;
int point2;
for(int time=0;time<n;time++)
{
if(used[time])//如果这个点在序列里面,那么就要从这个点找最短路径;
{
for(int time1=0;time1<n;time1++)
{
if(!used[time1]&&edge[time][time1]<_MIN){_MIN=edge[time][time1];point1=time;point2=time1;}
}
}
}
sum+=_MIN;
//point1,point2 分别记录了连通块序列中的点到非通块中的点的距离最短的两个点;
used[point2]=true;
//每次dfs都能确定一个点,所以在主函数里面调用n-1次就行了
}
这个函数主体是两个for,第一个for会看到进去马上有一个判断,你这个time是否用过了?这个用过的意思就是我的注释里面:如果这个点在序列里面,那么就从这个点找最短路径,为了简化,我们从一开始看起:
只有一个0号点被我used掉,也就是我的序列里面,或者说连通块里面,只有一个0号点,从这个0号点,已经有了floyd求出任意两点的最短路径,我用了贪心手段:
结果肯定会是一个无环图,因为有环对于连通块来说增加了一个无用的边。
我们设:现在有一个1——(n-1)为一个连通块,而且已经达到了最短边权和,满足条件:那么0号点他要和这个大连通块的所有点达成联系,只能是MIN=(MIN,edge[0][k]),k是1——(n-1)这n-1个点,现在MIN就是0到达这个连通块的最短路径了,
那根据我两个for,可以找出已经构成的(0——k个)这样的连通块,找到连通块中的每一个点到达非连通块中的点的最小值,然后再从这些点中取最小,那么每次都有这样的贪心算法,就可以每次执行这个函数都可以获得一个确定点,
这个算法有点近水楼台先得月的意味在。
完整代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <queue>
#include <stack>
using namespace std;
#define INF 0x3f3f3f3f
#define ULL unsigned long long
int n;
int sum=0;
int edge[105][105];
bool used[105];
void dfs()
{
int _MIN=INF;
int point1;
int point2;
for(int time=0;time<n;time++)
{
if(used[time])//如果这个点在序列里面,那么就要从这个点找最短路径;
{
for(int time1=0;time1<n;time1++)
{
if(!used[time1]&&edge[time][time1]<_MIN){_MIN=edge[time][time1];point1=time;point2=time1;}
}
}
}
sum+=_MIN;
//point1,point2 分别记录了连通块序列中的点到非通块中的点的距离最短的两个点;
used[point2]=true;
//每次dfs都能确定一个点,所以在主函数里面调用n-1次就行了
}
int main()
{
while(~scanf("%d",&n)&&n)
{
sum=0;
memset(edge,INF,sizeof(edge));
for(int time=0;time<(n*(n-1))/2;time++)
{
int from,to,value;
scanf("%d %d %d",&from,&to,&value);
edge[from-1][to-1]=value;
edge[to-1][from-1]=value;
}
//input finished
//floyd
for(int time=0;time<n;time++)
{
for(int time1=0;time1<n;time1++)
{
for(int time2=0;time2<n;time2++)
{
edge[time1][time2]=min(edge[time1][time2],edge[time1][time]+edge[time][time2]);
edge[time2][time1]=edge[time1][time2];
}
}
}
fill(used,used+n,false);
used[0]=true;
for(int time=0;time<n-1;time++)dfs();
printf("%d\n",sum);
}
return 0;
}