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

附赠题目链接:\(\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\) 规划:规划

posted @ 2023-06-16 09:08  Ciaxin  阅读(24)  评论(0编辑  收藏  举报