POJ 2728 Desert King (最优比率生成树)

题目类型  最优比率生成树

题目意思
给出最多 1000 个点与这些点之间的边的长度与花费 找一棵生成树 使  Sum(边的花费) / Sum(边的长度) 的值最小 输出这个值

解题方法

这道题求的是最小值
设边的花费为行向量 [c1, c2, ... , cn] 边的长度为行向量 [d1, d2, ... , dn] 选边的情况为列向量 [x1 ; x2 ; .. ; xn]
则求 r = (c*x) / (d*x) 的最小值
设 f(L) = c*x - L*(d*x) 即 f(L) = (c - L*d) x, r最小即L最小
记 z(L) 为 f(L)的最小值 令 x*为分数规划的最优解 令L* = (cx*) / (dx*) (即最小的r)
z(L) > 0 当且仅当 L < L*
z(L) = 0 当且仅当 L = L*
z(L) < 0 当且仅当 L > L*
因此可以用二分法求解, 二分L 对于每个mid求一次z(mid) 如果z(mid)>0则 l = mid 如果 z(mid) < 0 则r = mid

这道题 c 即边的花费 d即边的长度 
二分 L (最小值为0, 最大值为Sum(c)) 时找 f(L) = (c - L*d)x的最小值 如果把 c-L*d 当作边的权值 那么就是求这些边权下的最小生成树

由于这道题是完全图 而kruscal算法的时间复杂度是O(eloge) 会导致超时 因此可以使用时间复杂度为O(n*n)的prime算法求最小生成树

参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>

using namespace std;

const int maxn = 1000 + 10;
const int INF = 1<<29;

struct Node {
	int x, y, z;
}node[maxn];
double d[maxn][maxn], w[maxn][maxn], cd[maxn][maxn];

int Abs(int x) { return x < 0 ? -x : x; }

double dis(int a, int b) {
	double x = node[a].x - node[b].x;
	double y = node[a].y - node[b].y;
	return sqrt( x*x + y*y );
}

double cal(double x, int n) {
	for( int i=0; i<n; i++ ) for( int j=0; j<n; j++ ) cd[i][j] = w[i][j] - x * d[i][j];
	double sum = 0;
	double dis[maxn];
	bool vis[maxn];
	memset(vis, 0, sizeof(vis));
	vis[0] = true;
	for( int i=1; i<n; i++ ) dis[i] = cd[0][i];
	dis[0] = 0;
	for( int i=1; i<n; i++ ) {
		double nmin = INF;
		int mark = 0;
		for( int j=1; j<n; j++ ) {
			if(vis[j] == true) continue;
			if(nmin > dis[j]) {
				nmin = dis[j];
				mark = j;
			}
		}
		sum += nmin;
		vis[mark] = true;
		for( int j=1; j<n; j++ ) {
			if(vis[j] == true) continue;
			if(dis[j] > cd[mark][j]) dis[j] = cd[mark][j];
		}
	}
	return sum;
}

int main() {
	freopen("in", "r", stdin);
	int n;
	while(scanf("%d", &n), n) {
		for( int i=0; i<n; i++ ) {
			scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].z);
		}
		double sum = 0;
		for( int i=0; i<n; i++ ) {
			for( int j=i+1; j<n; j++ ) {
				d[j][i] = d[i][j] = dis(i, j);
				w[j][i] = w[i][j] = Abs(node[i].z-node[j].z);
				sum += d[i][j];
			}
		}
		double l = 0, r = 1e10;
		//printf("l = %lf r = %lf\n", l, r);
		while(r-l>1e-4) {
			double mid = (l+r)/2;
			//printf("cla = %lf mid = %lf\n", cal(mid, n), mid);
			if(cal(mid, n) < 0) r = mid;
			else l = mid;
			//return 0;
		}
		printf("%.3f\n", l);
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值