题意:给定若干点对(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;
}