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

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
图论:https://blog.csdn.net/weixin_39778570/article/details/87825212
题目链接:http://poj.org/problem?id=2728

题目描述

大卫大帝刚刚成为沙漠国家的国王。为了赢得人民的尊重,他决定在全国各地修建渠道,为每个村庄送水。与他的首都村庄相连的村庄将被供水。作为统治国家的统治者和智慧的象征,他需要以一种最优雅的方式建立渠道。

经过几天的学习,他终于想出了自己的计划。他希望将每英里通道的平均成本降至最低。换句话说,必须将信道的总成本与总长度的比率降至最低。他只需要建立必要的渠道,将水输送到所有的村庄,这意味着将只有一种方法来连接每个村庄到首都。

他的工程师调查了这个国家,并记录了每个村庄的位置和海拔高度。所有渠道必须在两个村庄之间直通,并水平修建。由于每两个村庄所处的海拔高度不同,他们得出结论认为,两个村庄之间的每一条水道都需要一个垂直的提水机,它可以将水提上来,也可以让水流下来。河道的长度是两个村庄之间的水平距离。通道的成本是升降机的高度。你应该注意到,每个村庄在不同的海拔高度,不同的渠道不能共用一个升降机。渠道可以安全交叉,没有三个村庄在同一条线路上。

作为大卫王的首席科学家和程序员,您被要求找到构建通道的最佳解决方案。

输入

有几个测试用例。每个测试用例都从包含数字N(2<=N<=1000)的一行开始,这就是村庄的数量。下列N行中的每一行都包含三个整数:x,y和z(0<=x,y<100000<=z<10000000)。(x,y)是村庄的位置,z是高度。第一个村庄是首都。N=0的测试用例结束输入,不应进行处理。

输出

对于每个测试用例,输出一行包含一个十进制数,这是通道的总开销对总长度的最小比率。这个数字应该四舍五入到小数点后三位数。

分析

很明显是要求一棵最小生成树,使得 s u m ( c o s t ) / s u m ( l e n ) sum(cost)/sum(len) sum(cost)/sum(len)最小
s u m ( c o s t ) / s u m ( l e n ) sum(cost)/sum(len) sum(cost)/sum(len)=ans
===> s u m ( c o s t ) − s u m ( l e n ) ∗ a n s = 0 sum(cost) - sum(len)*ans=0 sum(cost)sum(len)ans=0
===> s u m ( c o s t [ i ] − l e n [ i ] ∗ a n s ) = 0 sum(cost[i] - len[i]*ans)=0 sum(cost[i]len[i]ans)=0
于是我们可以二分答案, s u m ( c o s t [ i ] − l e n [ i ] ∗ a n s ) > 0 sum(cost[i] - len[i]*ans)>0 sum(cost[i]len[i]ans)>0则说明ans小了,否则ans大了
由于这幅图是一幅完全图,所以我们优先考虑prim算法
这个算法是先确定一个点在树里,然后寻找离这颗树最近的点,并入树中,然后使用这个刚并入树中的点更新其他点到树的最小距离,直到所有点都并入树中。

code
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int N=1005,INF=0x3f3f3f3f;
const double eps = 1e-6;
struct node{
	int x,y,z;
}a[N];
int n;
double d[N][N],c[N][N],cost[N][N],dis[N];
bool v[N];
double dist(int i, int j){
	return sqrt((a[i].x-a[j].x)*(a[i].x-a[j].x) + (a[i].y-a[j].y)*(a[i].y-a[j].y));
}
bool ok(double mid){
	fo(i,1,n){
		fo(j,i,n){
			if(i==j)c[i][j]=INF;
			else c[i][j] = c[j][i] = cost[i][j] - mid*d[i][j]; // 移项 
		}
	}
	fo(i,1,n)dis[i] = INF;
	memset(v, 0, sizeof(v));
	dis[1] = 0; // prim 随便一个起点 
	double ans = 0;
	while(1){
		int x = 0;
		fo(i,1,n)
			if(!v[i] && (!x || dis[x]>dis[i])) x = i;
		if(!x)break;
		v[x] = 1;
		ans += dis[x]; // 并入一个点点花费
		// 根据新点更新其他点
		fo(i,1,n) dis[i] = min(dis[i], c[x][i]); 
	}
	return ans>0; // 说明mid小了 
}
int main(){
	while(scanf("%d",&n)&&n){
		double L=0,R=0;
		fo(i,1,n){
			scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z); 
		}
		fo(i,1,n){
			fo(j,1,n){
				d[i][j] = d[j][i] = dist(i,j);
				cost[i][j] = cost[j][i] = abs(a[i].z - a[j].z);
				R += cost[i][j];
			}
		}
		while(R-L>=eps){
			double mid = (L+R)/2;
			if(ok(mid)){
				L = mid;
			}else R = mid;
		}
		printf("%.3f\n", L);
	}
	return 0;
} 

你可能感兴趣的:(poj)