hdoj 1875 畅通工程再续 (最小生成树之prim算法)

本文详细介绍了Prim算法,一种解决最小生成树问题的方法,适用于图论中的畅通工程问题。通过该算法,可以找到连接所有顶点的最经济路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                                                             最小生成树

n使用不同的遍历图的方法,可以得到不同的生成树;从不同的顶点出发,也可能得到不同的生成树。
n按照生成树的定义,n个顶点的连通网络的生成树有 n个顶点、n-1条边。
n构造最小生成树的准则
n必须使用且仅使用该网络中的n-1条边来联结网络中的 n个顶点;
n不能使用产生回路的边;
n各边上的权值的总和达到最小。
       

  prim算法


普里姆算法的基本思想

    从连通网络 N = { V, E }中的某一顶点 u0 出发,选择与它关联的具有最小权值的边( u0, v ),将其顶点加入到生成树顶点集合U中。
   以后每一步从一个顶点在 U,而另一个顶点不在 U 中的各条边中选择权值最小的边(u, v),把它的顶点加入到集合 U中。如此继续下去,直到网络中的所有顶点都加入到生成树顶点集 U中为止。
     

采用邻接矩阵作为图的存储表示




分析以上算法,设连通网络有 n个顶点则该算法的时间复杂度为 O(n2), 它适用于边稠密的网络。
注意:当各边有相同权值时,由于选择的随意性,产生的生成树可能不唯一。当各边的权值不相同时,产生的生成树是唯一的。
 

下面是杭电的一道例题     hdoj1875


畅通工程再续

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 20017    Accepted Submission(s): 6281


Problem Description
相信大家都听说一个“百岛湖”的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现。现在政府决定大力发展百岛湖,发展首先要解决的问题当然是交通问题,政府决定实现百岛湖的全畅通!经过考察小组RPRush对百岛湖的情况充分了解后,决定在符合条件的小岛间建上桥,所谓符合条件,就是2个小岛之间的距离不能小于10米,也不能大于1000米。当然,为了节省资金,只要求实现任意2个小岛之间有路通即可。其中桥的价格为 100元/米。
 

Input
输入包括多组数据。输入首先包括一个整数T(T <= 200),代表有T组数据。
每组数据首先是一个整数C(C <= 100),代表小岛的个数,接下来是C组坐标,代表每个小岛的坐标,这些坐标都是 0 <= x, y <= 1000的整数。
 

Output
每组输入数据输出一行,代表建桥的最小花费,结果保留一位小数。如果无法实现工程以达到全部畅通,输出”oh!”.
 

Sample Input
   
2 2 10 10 20 20 3 1 1 2 2 1000 1000
 

Sample Output
   
1414.2 oh!
 
  克鲁斯卡尔算法(已ac
<span style="font-size:14px;">#include<cstdio>
#include<algorithm>
#include<math.h>
using namespace std;
int per[103];
void init()
{
	for(int i=0;i<=102;i++)  //由于上边定义的是103数组,此时只能访问0~102;不能用103这个位置,否则越界 
	per[i]=i;
}
struct node
{
	int  x;
	int  y;

}dao[103];
struct node2
{
	int  a;
	int  b;
	double changdu; 
}p[103*103];
double cmp(node2 a,node2 b)
{
	return a.changdu<b.changdu;
}
int find(int x)
{
	if(x==per[x])
	return x;
	else
	return per[x]=find(per[x]);
}
int join(int x,int y)
{
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)
	{
		per[fy]=fx;
		return 1;
	}
	else
	return 0;
}
int main()
{
	int n,m,i,j;
	scanf("%d",&n);
	while(n--)
	{
		int x=0;
		scanf("%d",&m);
		init();       //初始化,容易丢 
		for(i=0;i<m;i++) 		
		scanf("%d%d",&dao[i].x,&dao[i].y); //这也可以定义成两个 数组来存储 
		for(i=0;i<m;i++)
		{
		  for(j=i+1;j<m;j++)
			{
		double  f=(double)(dao[j].x-dao[i].x)*(dao[j].x-dao[i].x)+(double)(dao[j].y-dao[i].y)*(dao[j].y-dao[i].y);
			p[x].changdu=sqrt(f); //注意前一句的强制转化,也可以不写double,写成1.0*后边的等式 
			p[x].a=i;
			p[x].b=j;
			x++;
			}			
		}	
			sort(p,p+x,cmp);
			double sum=0;
			int    cnt=1;
		for(i=0;i<x;i++)
		{
			if(join(p[i].a,p[i].b)&&p[i].changdu>=10&&p[i].changdu<=1000)	
			{
				sum=sum+p[i].changdu;
				cnt++;
			}
		} 
		if(cnt>=m)
		printf("%.1lf\n",sum*100);   //double输出用lf 
		else
		printf("oh!\n") ;              	
	}
	return 0;
}</span>


用克鲁斯卡尔大致有几个过程
1、建立编号。(找到两个点之间的联系,然后编号成第几组)
2、按照最小代价排序(一般为结构体排序)
3、利用并查集,边连接,边判断,排除成环的情况,并记录连接点的个数
4、判段连接点个数是否等于所需连接物体数量。
prim算法(ac)
#include<stdio.h>
#include<string.h>
#include<math.h>
#define max 1000
#define INF 0xfffffff
int n;
int x[110],y[110];
double g[110][110];  
double dis(int a,int b)
{
	return sqrt(1.0*(x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
void prim()
{
	double dis[110],min,sum;
	int vis[110];
	int v,i,j,k;
	memset(vis,0,sizeof(vis));   //此数组为1时,代表进入集合,否则没进入集合 
	for(i=1;i<=n;i++)
	dis[i]=g[1][i];  //记录i点到集合1的距离 
	vis[1]=1;        //让第一个点进入集合 
	 for(v=1;v<n;v++)
	 {
	 	min=INF;
		// k=1;   //可以不初始化,因为每次k都是集合外的点距离集合 内点的最近距离坐标,自动更新 
		 for(i=1;i<=n;i++)
		 if(!vis[i]&&dis[i]<min)
		 {
		 	min=dis[i];  //把距离1的最近距离赋给了min 
		 	k=i;		//把点的坐标赋给了k 
		 }
		 if(min==INF)   //说明外面的点要想进来,距离都不符合题意 
		 {
		 	printf("oh!\n");
		 	return ;
		 }
		 vis[k]=1;
		 for(i=1;i<=n;i++)
		 if(!vis[i]&&dis[i]>g[k][i])
		 dis[i]=g[k][i];   //注意dis记录的是集合内部距离集合外每一个点的最小距离,不是某个点 
	 } 
	    sum=0;
	   for(i=2;i<=n;i++)
	   sum+=dis[i]*100.0;
	   printf("%.1lf\n",sum) ;
	   return ;	 			 		
}
int main()
{
	int i,j,m;
	scanf("%d",&m);
	while(m--)
	{
		scanf("%d",&n);
		for(i=1;i<=n;i++)
		scanf("%d%d",&x[i],&y[i]);
		for(i=1;i<=n;i++)
			for(j=1;j<=n;j++)
			{
				g[i][j]=dis(i,j); //记录i点到j点的距离 
				if(g[i][j]<10||g[i][j]>1000)
				g[i][j]=INF;    //把不符合题意的点直接赋值无穷大,相当于做标记,便于输出 
			}
			prim();
	}
	return 0;
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值