poj 2728 最优比率生成树(国王修运河)

题意:给定若干点对(x,y,z),其中(x,y)表示点的坐标,z表示这个点的高度。这些点构成一个图, 每条边有花费C(高度差)和长度L(坐标的距离) ,这两个数都是非负整数。求一个生成树,使得花费之和与长度之和的比最小。即求r = (C1+C2+…+Cn-1)/(L1+L2+…+Ln-1) 最小

思路:最优比率生成树。先假设这个比例为r,则对于任意r应该有:r(min) <= r<= r(max).
生成树有n-1条边,则整理得:
r(max) * L1-c1+…+r(max) * Ln-1-cn-1>= 0;
r(min) *L1-c1+…+r(min) * Ln-1-cn-1<= 0.
因为我们这里要求r(min)所以现在重点看第二个式子。这个式子的意思是只要存在一个建生成树的方案,那么r(min)一定满足这个条件。反过来,也就是r(min)对所有的建生成树的方案都满足上述式子。

考虑不等式:r * L1-C1+…+r * Ln-1-Cn-1<= 0左边的值随着r增加而增加,随着r减少而减少。要使上式对任何一种生成树建法都成立,r可以无限小,但我们要求的答案r(min)是能使得上式在任何建法下都成立的最大的r的取值。(若x > r(min),则r(min)对应的取法,就能使r=x时不等式不成立)

所以具体的做法就是二分r,每次求最大生成树, 新图上最大生成树的取法,就对应于老图上一种生成树的取法。具体实现就一个二分加最大生成树就好。最大生成树的写法完全类似最小生成树。

#include <stdio.h>
#include <string.h>
#include <math.h>
#define INF 0x3fffffff
#define N 1005
struct node{
	int x,y,z;
}p[N];
double g[N][N],dis[N];
int n,used[N];
double distance(int i,int j){
	return sqrt((double)((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y)));
}
double prim(double r){
	int i,j,now;
	double max,res=0;
	memset(used,0,sizeof(used));
	used[0] = 1;//一下是标准prim过程,只不过求最大生成树
	for(i = 1;i<n;i++)
		dis[i] = r*g[0][i] - abs(p[0].z - p[i].z);//每个顶点与顶点0的距离
	for(i = 0;i<n-1;i++){
		max = -INF;
		for(j = 1;j<n;j++)
			if(!used[j] && dis[j] > max){
				now = j;
				max = dis[j];
			}
		used[now] = 1;
		res += max;
		for(j = 1;j<n;j++)
			if(!used[j] && dis[j]<r*g[now][j]-abs(p[now].z-p[j].z))//更新最大边的距离
				dis[j] = r*g[now][j]-abs(p[now].z-p[j].z);
	}
	return res;
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d",&n) && n){
		int i,j;
		double low,high,mid;
		memset(g,0,sizeof(g));
		for(i = 0;i<n;i++)
			scanf("%d %d %d",&p[i].x,&p[i].y,&p[i].z);
		for(i = 0;i<n-1;i++)
			for(j = i+1;j<n;j++)
				g[i][j] = g[j][i] = distance(i,j);
		low = 0,high = 100;//二分最优比率r
		while(high - low > 1e-7){
			mid = (low+high)/2;
			if(prim(mid)>=0)//如果最大生成树的权值大于等于0,则表示当前r可以达到
				high = mid;
			else
				low = mid;
		}
		printf("%.3lf\n",high);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值