POJ2728 - Desert King(最优比率生成树)
题目链接 https://cn.vjudge.net/problem/POJ-2728
【题意】
有个村庄()这些村庄在不同坐标和海拔,现在要对所有村庄供水,每两个村庄之间只有一条通道即可。建造通道的距离为村庄之间的欧几里德距离,费用则为村庄间的海拔之差。现在要求一种方案使得总费用与总距离的比值最小,问你最小的比值。
【思路】
用表示 村庄修造通道的费用, 为 村庄修造通道的距离。
方法一:二分法
设最小比值
构造函数
则取最小比值时,有 (其中 里面的 这条边是当前生成树的边)
实现过程:
枚举比值,求出所有的值。然后跑一次,求出构造最小生成树的值总和, 当枚举的值就是最优值的时候,有 在枚举过程中,若,说明 值过大;若 ,说明 值过小。这个题直接写裸的比用堆优化过的还快,不知道为啥.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const double inf=2e9;
const double eps=1e-6;
const int maxn=1005;
int n,m;
double x[maxn],y[maxn],h[maxn];
double cost[maxn][maxn],len[maxn][maxn],g[maxn][maxn];
double le,ri,mid;
bool done[maxn];
double low[maxn];
bool check(double r){
for(int i=0;i<n;++i){
for(int j=i+1;j<n;++j){
g[i][j]=g[j][i]=cost[i][j]-r*len[i][j];
}
}
int s=0;
for(int i=0;i<n;++i){
done[i]=false;
low[i]=g[s][i];
}
done[s]=true;
double ans=0;
for(int cnt=0;cnt<n-1;++cnt){
int id=-1;
double Mind=inf;
for(int i=0;i<n;++i){
if(!done[i] && low[i]<Mind){
Mind=low[i];
id=i;
}
}
ans+=Mind;
done[id]=true;
for(int i=0;i<n;++i){
if(!done[i] && g[id][i]<low[i]) low[i]=g[id][i];
}
}
return ans>=0;
}
int main(){
while(scanf("%d",&n)==1 && n){
for(int i=0;i<n;++i) scanf("%lf%lf%lf",&x[i],&y[i],&h[i]);
le=ri=0;
for(int i=0;i<n;++i){
for(int j=i+1;j<n;++j){
cost[i][j]=cost[j][i]=fabs(h[i]-h[j]);
len[i][j]=len[j][i]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
ri=max(ri,cost[i][j]/len[i][j]);
}
}
while(le+eps<ri){
mid=(le+ri)/2;
if(check(mid)) le=mid;
else ri=mid;
}
printf("%.3f\n",le);
}
return 0;
}
方法二:迭代 比二分高效
设 为当前生成树的最优比值
先给 赋初值(任意 条边的花费总和与长度总和的比值),用 值构造 值, ,并用变量 存储 的值;
以构造出的各边的 值,求最小生成树。在这里用两个变量 和 记录最小生成树中所有边的总花费和总长度。
结果
是我们用 (即先前的)求出的更优的值,更新
重复执行,直到 即求出的更优值 没有上一次的值小
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const double inf=2e9;
const double eps=1e-6;
const int maxn=1005;
int n,m;
double x[maxn],y[maxn],h[maxn];
double cost[maxn][maxn],len[maxn][maxn],g[maxn][maxn];
double r0,r1;
bool done[maxn];
int pre[maxn];
double low[maxn];
double prim(double r){
for(int i=0;i<n;++i){
for(int j=i+1;j<n;++j){
g[i][j]=g[j][i]=cost[i][j]-r*len[i][j];
}
}
int s=0;
for(int i=0;i<n;++i){
done[i]=false;
low[i]=g[s][i];
pre[i]=s;
}
done[s]=true;
double sumcost=0,sumlen=0;
for(int cnt=0;cnt<n-1;++cnt){
int id=-1;
double Mind=inf;
for(int i=0;i<n;++i){
if(!done[i] && low[i]<Mind){
Mind=low[i];
id=i;
}
}
sumcost+=cost[pre[id]][id];
sumlen+=len[pre[id]][id];
done[id]=true;
for(int i=0;i<n;++i){
if(!done[i] && g[id][i]<low[i]){
low[i]=g[id][i];
pre[i]=id;
}
}
}
return sumcost/sumlen;
}
int main(){
while(scanf("%d",&n)==1 && n){
for(int i=0;i<n;++i) scanf("%lf%lf%lf",&x[i],&y[i],&h[i]);
for(int i=0;i<n;++i){
for(int j=i+1;j<n;++j){
cost[i][j]=cost[j][i]=fabs(h[i]-h[j]);
len[i][j]=len[j][i]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
}
double sumcost=0,sumlen=0;
for(int i=1;i<n;++i){
sumcost+=cost[0][i];
sumlen+=len[0][i];
}
r0=sumcost/sumlen;
while(1){
r1=prim(r0);
if(r0-r1<eps) break;
r0=r1;
}
printf("%.3f\n",r0);
}
return 0;
}