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

来源:互联网 发布:买彩软件 编辑:程序博客网 时间:2024/04/28 19:25

                                                                             最小生成树

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
2210 1020 2031 12 21000 1000
 

Sample Output
1414.2oh!
 
  克鲁斯卡尔算法(已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;elsereturn 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;}elsereturn 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 elseprintf("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 0xfffffffint 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;}


0 0
原创粉丝点击