POJ 2728 Desert King(最优比例生成树)

题目大意

有n个村庄,村庄在不同坐标和海拔,现在要对所有村庄供水,只要两个村庄之间有一条路即可,建造水管距离为坐标之间的欧几里德距离,费用为海拔之差,现在要求方案使得费用与距离的比值最小,很显然,这个题目是要求一棵最优比率生成树。

解法

有带权图G, 对于图中每条边e[i], 都有benefiti和costi, 我们要求的是一棵生成树T, 它使得 ∑(benefit[i]) / ∑(cost[i]), i∈T 最大(或最小).
设x[i]等于1或0, 表示边e[i]是否属于生成树.
则我们所求的比率 r = ∑(benefit[i] * x[i]) / ∑(cost[i] * x[i]), 0≤ i < m .
为了使 r 最大, 设计一个子问题—> 让 z = ∑(benefit[i] * x[i]) - l * ∑(cost[i] * x[i]) = ∑(d[i] * x[i]) 最大 (d[i] = benefit[i] - l * cost[i]) , 并记为z(l). 我们可以兴高采烈地把z(l)看做以d为边权的最大生成树的总权值.
然后明确两个性质:
 1. z单调递减
  证明: 因为cost为正数, 所以z随l的减小而增大.
 2. z( max(r) ) = 0
  证明: 若z( max(r) ) < 0, ∑(benefit[i] * x[i]) - max(r) * ∑(cost[i] * x[i]) < 0, 可化为 max(r) < max(r). 矛盾;
若z( max(r) ) >= 0, 根据性质1, 当z = 0 时r最大.
转自zhang20072844
但是上面讲得很清楚了,但是我依然理解了很久,首先我说明本题为什么找的是最小生成树。假设∑(benefit[i])/∑(cost[i])=min,我们知道d[i]是每条边的边权,假如说存在一个更优的d[j]使∑(benefit[i])/∑(cost[i])< min,很明显d[j]要小于d[i],于是本题很明显应该找最小生成树才能找到最小的比例。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
const int maxn = 1005;
const double inf = 0x3f3f3f3f;
const double eps = 1e-5;
int vis[maxn],x[maxn],y[maxn],z[maxn],pre[maxn];
double dis[maxn],cost[maxn][maxn],dist[maxn][maxn];
int n;

double prim(double rate){
    double sum = 0.0;
    for (int i = 1; i <= n; i++) pre[i] = 1, dis[i] = inf;
    dis[1] = 0;
    memset(vis, 0, sizeof(vis));

    for (int i = 1; i <= n; i++){
        double Min = inf;
        int tmp;
        for (int j = 1; j <= n; j++)
            if (!vis[j] && dis[j] < Min)
                Min = dis[tmp = j];
        vis[tmp] = 1;
        sum += Min;
        for (int j = 1; j <= n; j++)
            if (dis[j] > cost[tmp][j] - dist[tmp][j]*rate){
                dis[j] = cost[tmp][j] - dist[tmp][j]*rate;
                pre[j] = tmp;
            }
    }
    return sum;
}

int main(){
    while(scanf("%d", &n) && n){
        for(int i = 1; i <= n; i++){
            scanf("%d%d%d", &x[i], &y[i], &z[i]);
            for (int j = 1; j < i; ++j){
                double tmp = (x[i] - x[j])*(x[i] - x[j]) + (y[i] - y[j])*(y[i] - y[j]);
                cost[i][j] = cost[j][i] = abs(z[i] - z[j]);
                dist[i][j] = dist[j][i] = sqrt(tmp);
            }
        }
        double l = 0,r = 1000000.0;
        while (r - l > eps){
            double mid = (l + r)/2.0;
            if (prim(mid) >= 0) l = mid;
            else r = mid;
        }
        printf("%.3f\n", r);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值