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;
}