背包问题 V3( $01$ 分数规划入门题)

发布时间 2023-06-16 09:15:12作者: Ciaxin

附赠题目链接:\(\text{51Nod-1257}\)

\(\text{description}\)

\(n\) 个物品的体积为 \(w_1,w_2,\cdots,w_n\)\(w_i\) 为整数),与之相对应的价值为 \(p_1,p_2,\cdots,p_n\)\(p_i\) 为整数),从中选出 \(k\) 件物品(\(k\le n\)),使得单位体积的价值最大。

\(\text{sol}\)

对于题目中的单位体积的价值可以理解为:

\[\max\left\{\frac{\sum_{i=1}^{k}p_i}{\sum_{i=1}^{k}w_i}\right\} \]

其中 \(p_i\) 的选取同时会影响到分子和分母的大小,从而影响最终答案,因此无法进行贪心选择。

通过 \(01\) 分数规划解决问题,将原本的问题进行转换。

首先令答案为 \(x\),那么有 \(x=\left(\sum_{i=1}^{k}p_i\right){/}\left(\sum_{i=1}^{k}w_i\right)\)

将其修改得到其变式为

\[\sum_{i=1}^{k}p_i-x\sum_{i=1}^{k}w_i=0 \]

那么就是说这个答案 \(x\) 是一定存在一个方案使得上述式子为 \(0\)

由此,假如一个 \(x'\) 使得上述公式的最大值大于 \(0\) 的话,就表示还有更优的答案 \(x\),相反同理。

那么就可以考虑二分答案,将每个物品的权值看作 \(p_i-x\times w_i\)

这时候我们只需要选择 \(k\) 个数,判断其最大值是否大于 \(0\) 即可,此时可以选择贪心求解。

\(\text{CODE}\)

//
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const double eps=1e-5;
double l,r,d[100100],tmp;
int a[100100],b[100100],tmpk[100100];
int tmpp,tmpq,n,k,fq,fp,fk;
int gcd(int a,int b) {
    while(b^=a^=b^=a%=b);
    return a;
}
bool check(double x) {
    tmp=0.00; tmpp=tmpq=0;
    for(int i=1;i<=n;i++) {
        d[i]=1.00*a[i]-1.00*b[i]*x;
        tmpk[i]=i;
    }
    sort(tmpk+1,tmpk+1+n,[](int x,int y){
        return d[x]>d[y];
    });
    for(int i=1;i<=k;i++) {
        tmp+=d[tmpk[i]];
        tmpq+=a[tmpk[i]];
        tmpp+=b[tmpk[i]];
    }
    return tmp>=eps;
}
int main() {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) {
        scanf("%d%d",&b[i],&a[i]);
    }
    l=0.00; r=100000.00;
    while(r-l>eps) {
        double mid=(l+r)/2.00;
        if(check(mid)) {
            l=mid;
            fq=tmpq; fp=tmpp;
        }
        else r=mid;
    }
    fk=gcd(fq,fp);
    printf("%d/%d",fq/fk,fp/fk);
    return 0;
}

\(\text{Else}\)

在判断二分的答案是否合法的时候,有时候需要选择不只是贪心的其他方法,例如:动规等。

同样的是一道入门题:Dropping tests

涉及到树上问题的 \(01\) 规划:规划