Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 21361 | Accepted: 5974 |
Description
Input
Output
Sample Input
4 0 0 0 0 1 1 1 1 2 1 0 3 0
Sample Output
1.000
Source
01分数规划,最优比例生成树。
用两种方法:
1.二分法
二分一个答案k,
如果有sigma(Hi)/sigma(Di)<k的,即sigma(Hi)-k*sigma(Di)<0,则k要缩小。
那么我们就求出以Hi-k*Di为边权的最小生成树,看这棵树的权值和是否<0即可。
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <cstdlib> #include <cmath> #define eps 1e-6 using namespace std; int n,m,v[1005]; double d[1005]; struct edge { double h,d; }a[1005][1005]; struct village { double x,y,h; }vi[1005]; double Getdis(int x,int y) { return sqrt((vi[x].x-vi[y].x)*(vi[x].x-vi[y].x)+(vi[x].y-vi[y].y)*(vi[x].y-vi[y].y)); } double prim(double k) { for (int i=1;i<=n;i++) v[i]=0,d[i]=100000000.0; d[1]=0.0; double ans=0.0; for (int i=1;i<=n;i++) { double minn=100000000.0; int x=-1; for (int j=1;j<=n;j++) if (!v[j]&&d[j]<minn) { x=j; minn=d[j]; } v[x]=1; ans+=d[x]; for (int j=1;j<=n;j++) if (!v[j]&&d[j]>a[x][j].h-k*a[x][j].d) d[j]=a[x][j].h-k*a[x][j].d; } return ans; } void Solve() { double l=0.0,r=1000.0; while (r-l>eps) { double m=(l+r)/(double)2; if (prim(m)<0) r=m; else l=m; } printf("%.3f\n",l); } double abss(double x) { if (x>0) return x; return -x; } int main() { while (scanf("%d",&n)!=EOF&&n) { for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&vi[i].x,&vi[i].y,&vi[i].h); for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) { a[i][j].h=a[j][i].h=abss(vi[i].h-vi[j].h); a[i][j].d=a[j][i].d=Getdis(i,j); } Solve(); } return 0; }
一开始为什么一直WA呢?
我对sigma(Hi)/sigma(Di)<k这个式子的变法是k*sigma(Di)-sigma(Hi)>0,因为这里是>0所以要求最大生成树!
2.Dinkelbach
这个方法是先随便给出一个答案l,然后根据这个l求最小生成树得到一个比率是k,这个k既然要比l更优,那么我们直接
用k迭代下去就可以,就不需要漫无目的的二分下去了。可以证明k最终会收敛到一个值上,这个值就是答案。
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <cstdlib> #include <cmath> #define eps 1e-6 using namespace std; int n,m,v[1005]; double d[1005]; double aa[1005],bb[1005]; struct edge { double h,d; }a[1005][1005]; struct village { double x,y,h; }vi[1005]; double p,q; double Getdis(int x,int y) { return sqrt((vi[x].x-vi[y].x)*(vi[x].x-vi[y].x)+(vi[x].y-vi[y].y)*(vi[x].y-vi[y].y)); } double prim(double k) { for (int i=1;i<=n;i++) v[i]=0,d[i]=100000000.0; d[1]=0.0; p=q=0.0; for (int i=1;i<=n;i++) { double minn=100000000.0; int x=-1; for (int j=1;j<=n;j++) if (!v[j]&&d[j]<minn) { x=j; minn=d[j]; } v[x]=1; p+=aa[x],q+=bb[x]; for (int j=1;j<=n;j++) if (!v[j]&&d[j]>a[x][j].h-k*a[x][j].d) d[j]=a[x][j].h-k*a[x][j].d,aa[j]=a[x][j].h,bb[j]=a[x][j].d; } return p/q; } void Solve() { double l=10.0; while (1) { double k=prim(l); if (fabs(k-l)<eps) break; l=k; } printf("%.3f\n",l); } double abss(double x) { if (x>0) return x; return -x; } int main() { while (scanf("%d",&n)!=EOF&&n) { for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&vi[i].x,&vi[i].y,&vi[i].h); for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) { a[i][j].h=a[j][i].h=abss(vi[i].h-vi[j].h); a[i][j].d=a[j][i].d=Getdis(i,j); } Solve(); } return 0; }
这个方法要快很多。
但是有的题目是不适合用此法的,比如【POJ 3621】求负环这道题,很难去记录当前的比率是多少。
感悟:
1.在进行二分判断的时候,要注意如果是<0,就要找最小的看是否<0;如果是>0就要找最大的,看是否>0
2.对于Dinkelbach的证明,可以看这里。
我的大概理解是:通过当前的k值求得的新的比率要比k更优,我们一直取更优的,最终就会取到最优的了
原文地址:http://blog.csdn.net/regina8023/article/details/42676677