题目大意
有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);
}
}