NOJ 1056 地道 普里姆算法+贪心法

    复习算法至普里姆算法,书上的代码不全,在oj上找了一题巩固一下。

    普里姆算法应用在最小代价生成树一类的题型中,其主要执行思想是:选择一个点作为源点,此时生成树只包含一个源点,后从未加入生成树的点中选择一条到生成树中的点的最小边加入生成树,将边的另一端点也加入生成树,所选的边(u,v)都是u点在生成树上v点在生成树外,由于这个性质所以不会产生回路。具体讲解不赘述,可自行查阅。

    课本上的代码只给出了较为关键的那一段,且边的存储方法是邻接表的形式,见到指针实在头疼,所以找一道题做一做,顺便自己写一下prim算法的代码实现。

    见题目:

地道

时间限制(普通/Java) :  1000 MS/ 3000 MS          运行内存限制 : 65536 KByte
总提交 : 379            测试通过 : 108 

比赛描述

话说南京的城市规划一般一般,各个大学分布极不合理,难于沟通。
我们夜猫族打算用一种常人难以想象的方式建立大学通道:用地道使得所有大学都相通。
但地道的造价不菲,而大学生是贫困群体,所以我们希望用尽量小的代价。
已知建设一条地道的费用和地道的距离成正比。其关系是,一个单位的距离需要的花费是7个ACM币,在ACM世界里货币的换算方法简单极了,29个ACM币等于一个DS币,17个DS币等于一个算法币。(ACM币单位为ac,DS币单位为ds,算法币单位为al)
但是学校太多了,而且有些学校不能直接连接(比如,跨湖或跨江地道太难建设了)。需要聪明的你的帮助。

输入

第一行包含两个整数N,M。N表示学校总数(1≤N≤100),M表示所有能直接连接的学校的数量(1<=M<=N*(N-1)/2)。
以下M行,每行三个正整数,第一个数和第二个数为学校编号,第三个为这两个学校间的距离L(0<=L<=10000)。

输出

若干带单位(ac,ds或al)的正整数,数字要尽可能小,单位复杂一点无妨(即把单位(ac,ds,al)转换得尽可能大,能用大单位表示尽量用大单位)数与单位间无空格。

样例输入

4 6
1 2 4
1 3 9
1 4 21
2 3 8
2 4 17
3 4 16

样例输出

6ds22ac

题目分析:这道题目很明显是最小代价生成树类型的题目,在计算出最小生成结果后使用贪心法实现最终问题结果的输入。

贴上代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int maxn =101;
const int INF =10000000 ;//以大数代表无穷大
int a[maxn][maxn];  //用二维数组记录两地之间的路径 
int min_dis[maxn];   // 例如 min_dis[2] 意思指 2这一点到已生成树中点的最小距离 
int  vis[maxn];  //标记是否已加入生成树 
int n,m;
int main()
{
	while(scanf("%d %d",&n,&m)==2)
	{
		int u,v,d;
		memset(vis,0,sizeof(vis));
		//	memset(min_dis,INF,sizeof(min_dis));  //不可以用memset赋值很大的数 
		//	memset(a,INF,sizeof(a));    //初始化   
		for(int i=1; i<=n; ++i) 				//老老实实用for循环初始化   
		{
			min_dis[i] = INF;
			for(int j=1; j<=n; ++j)
				a[i][j] = INF;
		}
		for(int i=0; i<m; i++)
		{
			scanf("%d %d %d",&u,&v,&d);
			a[u][v]=d;
			a[v][u]=d;
		}
		//下面是prim算法
		int k=1;
		long sum=0;
		for(int i=1; i<n; i++)
		{
			min_dis[k]=0;    //初始时将点1加入生成树  
			vis[k]=1; 
         //此for循环是更新每个未加入的点到已加入 的点的最小值   
		 //因为每次大的循环执行一次 都需要更新 一次
		 //第一次执行后 min_dis值为 剩余各点到第一个加入的点的距离
		 //第二次执行  若到达第二个点的值小于到达第一个点的值 则更新  否则不变化 
			for(int j=1; j<=n; j++)  
			{
				if(!vis[j]&&min_dis[j]>a[k][j])
					min_dis[j]=a[k][j];
			}
			//下面是求出 按算法要求求出符合要求的最短边 
			int mincost=INF;
			for(int j=1; j<=n; j++)
			{
				if(!vis[j]&&mincost>min_dis[j])
				{
					mincost=min_dis[j];
					k=j;  //将k置为j  下次循环开始时将其加入标记为已知 
				}
			}
			if(mincost==INF)  //剪枝 写不写没什么影响 
				break;
			sum+=mincost;     
		}
		sum *= 7; //下面就是一个非常简单的相当于进制转换  非常容易理解的贪心法 
		if(!sum)
		{
			printf("0ac\n");
			return 0;
		}
		if(sum/493)
		{
		    printf("%dal",sum/493);
			sum %= 493;
		}
		if(sum/29)
		{
			printf("%dds",sum/29);
			sum %= 29;
		}
		if(sum)
		{
		    printf("%dac",sum);
		}
		printf("\n");
	}

}
    上述代码已经有非常详细的注释,总结一下普里姆算法的代码实现形式,每次选边都是一个for循环,每个选边的过程主要包括两个方面,一是更新剩余点到生成树中点的最小距离,第二个就是选出最短的符合要求的边加入生成树即可。理解这两点就很容易理解普里姆算法。

    此题的贪心法最优量度标准太简单,不赘述。

    memset函数不可初始化大值,这一点需要注意,有时间好好探究。

    在敲这题目的时候遇到不少问题,但敲完之后还是有收获的,可见实践之重要性,终究是实践出真知。

    特记下,以备后日回顾。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值