K Best POJ - 3111(二分)

传送门

题意:有n个物品的重量和价值分别是wi和vi。从中选出k个物品使得单位重量的价值最大。

题解:首先考虑二分做法

那么一般最先想到的方法是把物品按照单位价值进行排序,从小到大贪心地进行选取。但是这个方法对于很多数据都有bug,所以是不行的。

实际上,对于这个问题使用二分搜索可以很好地解决,定义:

条件C(x):=可以选择使得单位重量的价值不小于x

因此,原来的问题就变成了求满足C(x)的最大的x。那么怎么判断C(x)是否可行呢?假设我们选了某个物品的集合S,那么它们的单位重量的价值是

\sum_{i\epsilon S}vi/ \sum_{i\epsilon S}wi

因此就变成了判断是否存在S满足下面的条件

\sum_{i\epsilon S} vi/\sum_{i\epsilon S}wi>=x

把这个不等式进行变形得到;

\sum_{i\epsilon S}(vi-x*wi)>=0

因此,可以对(vi-x*wi)的值进行排序贪心地进行选取。因此就变成了

C(x)=((vi-x*wi)从大到小排列中地前K个的和不小于0)

每次判断的复杂度是O(nlogn).

附上代码:(注意精度问题,以及二分玄学操作)

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn=1e5+50;
const int INF=1e7;

int n,k;
int v[maxn],w[maxn];

struct node{
    double val;
    int id;
};
node nodes[maxn];

bool cmp(node a,node b)
{
    return a.val>b.val;
}

int ok(double x)
{
    for(int i=0;i<n;i++){
        nodes[i].val=v[i]-x*w[i];
        nodes[i].id=i+1;
    }
    sort(nodes,nodes+n,cmp);
    double sum=0;
    for(int i=0;i<k;i++){
        sum+=nodes[i].val;
    }
    return sum>=0;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++){
        scanf("%d%d",&v[i],&w[i]);
    }
    double lb=-1,ub=INF;
    while(ub-lb>1e-8){
        double mid=(lb+ub)/2;
        if(ok(mid)){
            lb=mid;
        }else{
            ub=mid;
        }
    }
    for(int i=0;i<k;i++){
        printf("%d",nodes[i].id);
        if(i<k-1){
            printf(" ");
        }else{
            printf("\n");
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhouzi2018/article/details/83117041