http://poj.org/problem?id=2728
Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 20513 | Accepted: 5764 |
Description
Input
Output
Sample Input
4 0 0 0 0 1 1 1 1 2 1 0 3 0
Sample Output
1.000
题意:给出n个村庄的坐标(x,y,z),z代表海拔高度,一个国王要修一些水渠,是这些村庄联通起来,任意连个村庄之间的距离是他们水平坐标的欧几里得距离,花费是海拔的高度差,问联通后的所有花费和比上所有距离和最小是多少;
分析:设第i条边的费用是c[i],距离是l[i],由此可得出公式:r=sigma(c[i]*x[i])/sigma(l[i]*x[i]);(x[i]=0或者1表示是否要取该边)
设在z(l)=sigma(c[i]*x[i])-l*sigma(l[i]*x[i]);
变形得:r=sigma(c[i]*x[i])/sigma(l[i]*x[i])=l+z(l)/sigma(l[i]*x[i]);
首先给l附上一个较大的值。
当r有比l更小值的充要条件是z(l)<0,而要求z(l)的最小值,就是求sigma(c[i]*x[i])-l*sigma(l[i]*x[i])最小生成树,边的权值是d[i]=c[i]-l*l[i];然后用求最小生成树的方法来更新z(l)的值;
每次当z(l)有小于0的值时,就说明还存在比当前l更小的比率,然后就把比l较小的l1来更新边权值,再次求z(l1),当l越来越收敛于r时,z(l)也就趋于0;所以当z(l)=0的时候也就是比率最小的时候;
下面介绍两种方法一种是二分法(2400ms)和迭代法(255ms)
二分法:
首先去两个极端值left=0和right=100000即可,然后二分l值mid=(left+right)/2;当得到的z(mid)<0时,说明还有更优的答案此时right=mid,当z(mid)>0时,此时的mid是没有任何意义的,所以left=mid;知道right-left的值小于eps=0.00000001的精确度的时候mid就是最优答案;
迭代法:
首先给l随便附一个值a=0,然后建边求最小生成树z(a)的值,令b=z(l);加入fabs(b-a)<eps=0.000001的精确度时,b就是最优解,否则把b赋给a,然后重新建边求z的值,知道满足精确度是退出循环;
代码:
二分法:
#include"stdio.h" #include"string.h" #include"queue" #include"stdlib.h" #include"iostream" #include"algorithm" #include"string" #include"iostream" #include"map" #include"math.h" #define M 1005 #define eps 1e-6 #define inf 100000000 using namespace std; double G[M][M],dis[M]; int use[M]; double prime(int n) { double sum=0; memset(use,0,sizeof(use)); int i,j; for(i=1;i<=n;i++) dis[i]=G[1][i]; dis[1]=0; use[1]=1; for(j=1;j<n;j++) { double Min=inf; int tep=-1; for(i=1;i<=n;i++) { if(!use[i]&&Min>dis[i]) { Min=dis[i]; tep=i; } } if(tep==-1) break; use[tep]=1; sum+=Min; for(i=1;i<=n;i++) { if(!use[i]&&dis[i]>G[tep][i]) dis[i]=G[tep][i]; } } return sum; } double x[M],y[M],z[M]; double L[M][M],C[M][M]; int main() { int n,i,j; while(scanf("%d",&n),n) { for(i=1;i<=n;i++) scanf("%lf%lf%lf",&x[i],&y[i],&z[i]); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { L[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])); C[i][j]=fabs(z[i]-z[j]); } } double left=0; double right=10000000; double mid; while(right-left>eps) { mid=(left+right)/2; for(i=1;i<=n;i++) for(j=1;j<=n;j++) G[i][j]=C[i][j]-L[i][j]*mid; double ans=prime(n); if(ans<0) right=mid; else left=mid; } printf("%.3lf\n",mid); } }迭代法:
#include"stdio.h" #include"string.h" #include"queue" #include"stdlib.h" #include"iostream" #include"algorithm" #include"string" #include"iostream" #include"map" #include"math.h" #define M 1005 #define eps 1e-6 #define inf 100000000 using namespace std; double G[M][M],dis[M]; bool use[M]; double x[M],y[M],z[M]; double L[M][M],C[M][M]; double prime(int n)//注意有几个与prime算法不同的地方 { int i,j,k,pre[M]; double mini,b=0,c=0; for(i=1;i<=n;i++) { dis[i]=G[1][i]; pre[i]=1; use[i]=false; } dis[1]=0; use[1]=true; for(i=1;i<n;i++) { mini=inf; k=-1; for(j=1;j<=n;j++) { if(!use[j]&&mini>dis[j]) { mini=dis[j]; k=j; } } if(mini==inf)break; b+=L[pre[k]][k];//******************* c+=C[pre[k]][k];//******************* use[k]=true; for(j=1;j<=n;j++) { if(!use[j]&&dis[j]>G[k][j]) { dis[j]=G[k][j]; pre[j]=k;//记录每个节点的前驱 } } } return c/b;//返回比率 } int main() { int n,i,j; while(scanf("%d",&n),n) { for(i=1;i<=n;i++) scanf("%lf%lf%lf",&x[i],&y[i],&z[i]); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { L[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])); C[i][j]=fabs(z[i]-z[j]); } } double left=0; double right; while(1) { for(i=1;i<=n;i++) for(j=1;j<=n;j++) G[i][j]=C[i][j]-L[i][j]*left; right=prime(n); if(fabs(right-left)<eps)break; left=right; } printf("%.3lf\n",right); } return 0; }