POJ 2728 Desert King
http://poj.org/problem?id=2728
题目大意是:给你坐标上一些点,然后你需要用一些边把他们连接起来,边有费用和长度,求总费用和总长度最小比值。
即最优比率生成树,用01分数规划的Dinkelbach算法解决。
问题目标求 MIN( ∑CiXi / ∑DiXi ) Xi∈{0,1} ,设r=∑CiXi / ∑DiXi ,可得∑CiXi - ∑DiXi * r=0.
设Q(r)=∑CiXi - ∑DiXi * r = ∑(CiXi - DiXi*r), 即当Q(r)无限逼近0时得到问题的极值,所以我们将边权转化为(Ci-Di*r)不断求最小生成树即可。
(废话:为什么说这个算法是迭代……因为MST的返回值也是一个r,所以我们用当前解去限制下一次计算,就迭代了……纠结半天……另外Dinkelbach算法的收敛速度是超越二分的)
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #define sqr(r) (r)*(r) #define eps 1e-6 #define mn 1001 using namespace std; const double inf=100000000.0; int n,pre[mn]; double dist[mn][mn],d[mn],cost[mn][mn]; bool vis[mn]; struct POSITION{ int x,y,z; }pos[mn]; double prim(double x){ double cur_cost=0,cur_dist=0,mind; memset(vis,false,sizeof(vis)); d[1]=0,pre[1]=0,vis[1]=true; for(int i=2;i<=n;i++) d[i]=cost[1][i]-dist[1][i]*x,pre[i]=1; for(int k=1;k<n;k++){ int minp;mind=inf; for(int i=2;i<=n;i++) if(!vis[i] && mind>d[i]) mind=d[i],minp=i; vis[minp]=true; cur_cost+=cost[pre[minp]][minp]; cur_dist+=dist[pre[minp]][minp]; for(int i=2;i<=n;i++) if(!vis[i] && d[i]>cost[minp][i]-dist[minp][i]*x) d[i]=cost[minp][i]-dist[minp][i]*x,pre[i]=minp; } return cur_cost/cur_dist; } int main(){ while(scanf("%d",&n),n!=0){ memset(cost,0,sizeof(cost)); memset(dist,0,sizeof(dist)); for(int i=1;i<=n;i++) scanf("%d%d%d",&pos[i].x,&pos[i].y,&pos[i].z); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dist[i][j]=sqrt((double)sqr(pos[i].x-pos[j].x)+(double)sqr(pos[i].y-pos[j].y)), cost[i][j]=abs(pos[i].z-pos[j].z); double a=0,b; while(true){ b=prim(a); if(fabs(b-a)<eps) break; a=b; } printf("%.3lf\n",a); } return 0; }