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

题目链接:Click here~~

题意:

没看,直接搜的别人的建图。

解题思路:

调了一上午,哎。下面转自大神博客。


概念

有带权图G, 对于图中每条边e[i], 都有benifit[i](收入)和cost[i](花费), 我们要求的是一棵生成树T, 它使得 ∑(benifit[i]) / ∑(cost[i]), i∈T 最大(或最小).

0-1分数规划

设x[i]等于1或0, 表示边e[i]是否属于生成树.

则我们所求的比率 r = ∑(benifit[i] * x[i]) / ∑(cost[i] * x[i]), 0≤i<m .

为了使 r 最大, 设计一个子问题---> 让 z = ∑(benifit[i] * x[i]) - l * ∑(cost[i] * x[i]) = ∑(d[i] * x[i]) 最大 (d[i] = benifit[i] - l * cost[i]) , 并记为z(l). 我们可以兴高采烈地把z(l)看做以d为边权的最大生成树的总权值.

然后明确两个性质:

 1.  z单调递减

  证明: 因为cost为正数, 所以z随l的减小而增大.

 2.  z( max(r) ) = 0

  证明: 若z( max(r) ) < 0, ∑(benifit[i] * x[i]) - max(r) * ∑(cost[i] * x[i]) < 0, 可化为 max(r) < max(r). 矛盾;

          若z( max(r) ) >= 0, 根据性质1, 当z = 0 时r最大. 

到了这个地步, 七窍全已打通, 喜欢二分的上二分, 喜欢Dinkelbach的就Dinkelbach.


二分法。(1200MS)

#include <queue>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int N = 1e3 + 5;

double d[N],w[N][N];

bool vis[N];

double prim(int n)
{
    memset(vis,false,sizeof(vis));
    memset(d,127,sizeof(d));
    int u = 1 , times = 0;
    double ans = 0;
    while(++times < n)
    {
        vis[u] = true;
        for(int v=1;v<=n;v++)
            if(!vis[v] && w[u][v] < d[v])
                d[v] = w[u][v];
        double mmin = d[0];
        for(int v=1;v<=n;v++)
            if(!vis[v] && mmin > d[v])
                mmin = d[v] , u = v;
        if(mmin == d[0])
            return -1;
        ans += mmin;
    }
    return ans;
}

struct Point
{
    int x,y,z;
    void read(){
        scanf("%d%d%d",&x,&y,&z);
    }
    int sqr(int x){
        return x * x;
    }
    double dis(const Point& P){
        return sqrt( sqr(x-P.x)*1.0 + sqr(y-P.y) );
    }
    int cost(const Point& P){
        return abs(z-P.z);
    }
}A[N];

double dis[N][N];

int cost[N][N];

void build(int n,double L)
{
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            w[i][j] = w[j][i] = cost[i][j] - L*dis[i][j];
}

const double eps = 1e-5;

int main()
{
    int n;
    while(scanf("%d",&n),n)
    {
        for(int i=1;i<=n;i++)
            A[i].read();
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
            {
                dis[i][j] = dis[j][i] = A[i].dis(A[j]);
                cost[i][j] = cost[j][i] = A[i].cost(A[j]);
            }
        double l = 0 , r = 100;
        while(r-l > eps)
        {
            double mid = (l+r) / 2;
            build(n,mid);
            if(prim(n) > eps)
                l = mid;
            else
                r = mid;
        }
        printf("%.3f\n",r);
    }
    return 0;
}

迭代法。(250MS)

#include <queue>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int N = 1e3 + 5;

double dis[N][N];

int cost[N][N];

double d[N],w[N][N];

bool vis[N];

int pre[N];

double prim(int n)
{
    if(n == 1)
        return 0;
    memset(vis,false,sizeof(vis));
    memset(d,127,sizeof(d));
    int u = 1 , times = 0;
    double dis = 0;
    int cost = 0;
    while(++times < n)
    {
        vis[u] = true;
        for(int v=1;v<=n;v++)
            if(!vis[v] && w[u][v] < d[v])
                d[v] = w[u][v] , pre[v] = u;
        double mmin = d[0];
        for(int v=1;v<=n;v++)
            if(!vis[v] && mmin > d[v])
                mmin = d[v] , u = v;
        if(mmin == d[0])
            return -1;
        cost += ::cost[pre[u]][u];
        dis += ::dis[pre[u]][u];
    }
    return cost / dis;
}

struct Point
{
    int x,y,z;
    void read(){
        scanf("%d%d%d",&x,&y,&z);
    }
    int sqr(int x){
        return x * x;
    }
    double dis(const Point& P){
        return sqrt( sqr(x-P.x)*1.0 + sqr(y-P.y) );
    }
    int cost(const Point& P){
        return abs(z-P.z);
    }
}A[N];

void build(int n,double L)
{
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            w[i][j] = w[j][i] = cost[i][j] - L*dis[i][j];
}

const double eps = 1e-5;

int main()
{
    int n;
    while(scanf("%d",&n),n)
    {
        for(int i=1;i<=n;i++)
            A[i].read();
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
            {
                dis[i][j] = dis[j][i] = A[i].dis(A[j]);
                cost[i][j] = cost[j][i] = A[i].cost(A[j]);
            }
        double last = -1 , cur = 0;
        while(fabs(cur-last) > eps)
        {
            last = cur;
            build(n,last);
            cur = prim(n);
        }
        printf("%.3f\n",cur);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值