POJ 2728

传送门

题目大意

有n个村庄,每个村庄都有一个(x, y)坐标和z海拔,定义两个村庄间的dist为坐标的距离,cost为海拔差的绝对值,求图的一颗生成树,使得(frac{sum cost}{sum dist})最小。

题解

最小比例生成树的裸题。

看到(frac{sum cost}{sum dist})的分数形式,首先可以想到分数规划:

(ans = frac{sum cost}{sum dist})

(sum cost - ans * sum dist = 0)

那么可以二分答案ans,将边的权值都变成(cost - ans * dist),并求最小生成树:

  • 当ans较大时,最小生成树答案<0

  • 当ans为最优解时,最小生成树答案=0

  • 当ans较小时,最小生成树答案>0

code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;

#define eps 1e-7

const double OO = 0x3f3f3f3f;
int n;
double ans, dist[1050][1050], len[1050][1050];
double x[1050], y[1050], z[1050], cost[1050][1050];
double minlen[1050];
bool used[1050];

inline double check(double mid){
	for(int i = 1; i <= n; i++){
		len[i][i] = 0;
		used[i] = 0;
		minlen[i] = OO;
		for(int j = 1; j < i; j++)
			len[i][j] = len[j][i] = cost[i][j] - dist[i][j] * mid;
	}

	minlen[1] = 0;
	double ret = 0;
	while(true){
		int v = -1;
		for(int i = 1; i <= n; i++)
			if(!used[i] && (v == -1 || minlen[i] < minlen[v])) v = i;
		if(v == -1) break;
		ret += minlen[v];
		used[v] = true;
		for(int i = 1; i <= n; i++)
			if(!used[i]) minlen[i] = min(minlen[i], len[v][i]);
	}
	return ret;
}

int main(){
	freopen("h.in", "r", stdin);
	while(scanf("%d", &n), n){
		for(int i = 1; i <= n; i++){
			scanf("%lf %lf %lf", &x[i], &y[i], &z[i]);
			dist[i][i] = cost[i][i] = 0;
			for(int j = 1; j < i; j++){
				cost[i][j] = cost[j][i] = abs(z[i] - z[j]);
				dist[i][j] = dist[j][i] = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
			}
		}
		double l = 0, r = 1e6;
		for(int i = 1; i <= 50; i++){
			double mid = (l + r) / 2;
			if(check(mid) < 0) r = mid;
			else l = mid;
		}
		printf("%.3f
", (l + r) / 2);
	}
}
原文地址:https://www.cnblogs.com/CzYoL/p/7459925.html