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;

}

  

你可能感兴趣的:(des)