题目
http://acm.hdu.edu.cn/showproblem.php?pid=1233
问题分析
本题求是整个省畅通的最低成本,把村庄作为顶点,把公路作为边,那么本题就是求最小生成树的总边权值。
算法
算法核心
本题可采用Prim算法,其实这也是贪心的一种。设V为顶点的全集,U初始化{v0},每次都选择V-U中到分支U最近的点,然后把该点加入U,更新V-U中的点到U的距离,然后再次选择直至U=V。
算法流程
closest[]:V-U中的点到分支U的最近的点;
lowcost[]:V-U中的点到分支U的最短距离,也就是map_[j][closest[j]];
vis[]:是否访问,即是否在U中;
本题要求的是最小生成树,即使整个图连通且无圈的边权值的最小值,所以以哪个顶点为起始点都可以,不会影响最后的结果,我们就以V1为起始点为例。首先将closest[]数组初始化为1,U为{v1},即V-U中的顶点均与U中的v1离得最近,lowcost[i]也初始化为map_[i][1];然后遍历找到V-U中lowcost[]的最小值k,把该点加入U,即vis[k]=1;之后更新V-U的lowcost[]数组,如果到vk的距离小于原来的距离,就更新lowcost[],更新closest[];直至最后V-U为空集,然后遍历将lowcost[]中的值相加即为所求。
一点想法
求最小边权值一定是最后遍历lowcost[]数组,而不是像kruskal算法选一条边算一遍,因为在选择一个顶点加入U之后,可能会更新很多条边的值,但是下一步只会选择一条边,不知道更新的哪一条边会作为最小生成树的其中一边,所以无法在循环选顶点的循环中计算。
WA的教训
一定要分清边数和顶点数,尤其是循环比较多的时候,不然可能会像我一样惨兮兮的RE好几遍。。。。
代码实现
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=105;
const int INF=1<<29;
int n,m;//顶点数,边数
int closest[maxn];//V-U中离U最近的顶点
int lowcost[maxn];//V-U中顶点到U的最小边权值
int vis[maxn];//是否在U中
int map_[maxn][maxn];
int ans;//最低成本
void init()
{
for(int i=1; i<=n; ++i)
{
closest[i]=1;
lowcost[i]=map_[i][1];
}
memset(vis,0,sizeof(vis));
ans=0;
vis[1]=1;
lowcost[1]=0;
}
void Prim()
{
init();
int i,j,k,min_num;
for(i=1; i<=n; ++i)
{
min_num=INF;
k=0;
for(j=1; j<=n; ++j)//找到V-U中离U最近的顶点
if(!vis[j]&&lowcost[j]<min_num)
{
k=j;
min_num=lowcost[j];
}
if(k==0)//V-U为空说明算法结束
break;
vis[k]=1;
for(j=1; j<=n; ++j)//更新lowcost[]和closest[]
if(!vis[j]&&map_[j][k]<lowcost[j])
{
lowcost[j]=map_[j][k];
closest[j]=k;
}
}
}
int main()
{
int i,j,a,b,v;
while(scanf("%d",&n)==1&&n)
{
m=n*(n-1)/2;
memset(map_,0,sizeof(map_));
for(i=0; i<m; ++i)
{
scanf("%d%d%d",&a,&b,&v);
map_[a][b]=v;
map_[b][a]=v;
}
Prim();
for(i=1; i<=n; ++i)
ans+=lowcost[i];
printf("%d\n",ans);
}
return 0;
}