POJ2728 - Desert King(最优比率生成树)

题目链接 https://cn.vjudge.net/problem/POJ-2728

【题意】
N 个村庄( N <= 1000 )这些村庄在不同坐标和海拔,现在要对所有村庄供水,每两个村庄之间只有一条通道即可。建造通道的距离为村庄之间的欧几里德距离,费用则为村庄间的海拔之差。现在要求一种方案使得总费用与总距离的比值最小,问你最小的比值。

【思路】
c o s t [ i ] [ j ] 表示 i j 村庄修造通道的费用, l e n [ i ] [ j ] i j 村庄修造通道的距离。

方法一:二分法

设最小比值

o = c o s t [ i ] [ j ] l e n [ i ] [ j ]

构造函数 M a p [ i ] [ j ] = c o s t [ i ] [ j ] o l e n [ i ] [ j ]

则取最小比值时,有 M a p [ i ] [ j ] = 0 (其中 M a p [ i ] [ j ] 里面的 i > j 这条边是当前生成树的边)

实现过程:
枚举比值 m i d ,求出所有的 M a p 值。然后跑一次 p r i m ,求出构造最小生成树的 M a p 值总和 a n s , 当枚举的值 m i d 就是最优值 o 的时候,有 M a p [ i ] [ j ] = 0 在枚举过程中,若 a n s < 0 ,说明 o 值过大;若 a n s > 0 ,说明 o 值过小。这个题直接写裸的 p r i m 比用堆优化过的还快,不知道为啥.

#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;
}

方法二:迭代 比二分高效
x 为当前生成树的最优比值
先给 x 赋初值(任意 N 1 条边的花费总和与长度总和的比值),用 x 值构造 M a p 值, M a p [ i ] [ j ] = c o s t [ i ] [ j ] x l e n [ i ] [ j ] ,并用变量 x 0 存储 x 的值;

以构造出的各边的 M a p 值,求最小生成树。在这里用两个变量 s u m c o s t s u m l e n 记录最小生成树中所有边的总花费和总长度。

结果

s u m c o s t s u m l e n
是我们用 x 0 (即先前的 x )求出的更优的值,更新 x = s u m c o s t / s u m l e n
重复执行,直到 x >= x 0 即求出的更优值 x 没有上一次的值 x 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;
}

猜你喜欢

转载自blog.csdn.net/xiao_k666/article/details/82728411
今日推荐