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

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

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

【思路】
cost[i][j]表示 ij 村庄修造通道的费用,len[i][j]ij 村庄修造通道的距离。

方法一:二分法

设最小比值

o=cost[i][j]len[i][j]

构造函数 Map[i][j]=cost[i][j]olen[i][j]

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

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

#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 赋初值(任意 N1 条边的花费总和与长度总和的比值),用 x 值构造 Map 值, Map[i][j]=cost[i][j]xlen[i][j],并用变量 x0 存储 x 的值;

以构造出的各边的 Map 值,求最小生成树。在这里用两个变量 sumcostsumlen 记录最小生成树中所有边的总花费和总长度。

结果

sumcostsumlen
是我们用 x0(即先前的x)求出的更优的值,更新x=sumcost/sumlen
重复执行,直到 x>=x0 即求出的更优值 x 没有上一次的值x0

#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;
}
posted @ 2018-09-16 19:14  不想吃WA的咸鱼  阅读(124)  评论(0编辑  收藏  举报