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

  

原文地址:https://www.cnblogs.com/Delostik/p/2118848.html