【总结】背包问题的至多/恰好/至少

零、前导知识

0x3f3f3f3f数值大于1e9,且满足无穷大 + 无穷大 = 无穷大(不会溢出int

memset(f, 0xcf, sizeof f) -> -808464433
memset(f, -0x3f, sizeof f) -> -1044266559
memset(f, 0x3f, sizeof f) -> 1061109567

对于一维背包问题,注意区分该问题的下面这三种情况,即至多/恰好/至少,它们的状态转移方程其实是一样的差异在于初始化

一、体积至多是v时的最小/大值

这是背包问题中最常见的问法,取最小用\(min\),取最大用\(max\)。

方法:全部初始化为\(0\),且保证\(v\)大于等于\(0\)。
代表题目:\(AcWing\) \(423\) .采药,其代码如下:

#include<bits/stdc++.h>

using namespace std;
const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main() {
    //优化输入
    ios::sync_with_stdio(false);
    //物品个数n
    cin >> m >> n;
    //读入体积和重量
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    //01背包模板
    for (int i = 1; i <= n; i++)
        for (int j = m; j >= v[i]; j--)
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;
    return 0;
}

二、 体积恰好是v时的最小/大值

方法:\(f[0]\)初始化为\(0\),其余初始化为正/负无穷(分别对应题目问的最小/最大,代表不合法状态,不应该被选取),且保证\(v\)大于等于\(0\)。

代表题目:\(AcWing\) \(734\) 能量石,其代码如下:

#include <bits/stdc++.h>

using namespace std;
const int N = 110;  //能量石个数上限
const int M = 10010;//

//用来描述每个能量石的结构体
struct Node {
    int s;           //吃掉这块能量石需要花费的时间为s秒
    int e;           //可以获利e个能量
    int l;           //不吃的话,每秒失去l个能量
} q[N];              //能量石的数组,其实这里开的数组开的特别大了,题目中说是110就够了。

//结构体对比函数
bool cmp(const Node &a, const Node &b) {
    return a.s * b.l < b.s * a.l;
}

int n;               //能量石的数量
int f[M];            //f[i]:花i个时间得到的最大能量
int idx;             //输出是第几轮的测试数据

int main() {
    //优化输入
    ios::sync_with_stdio(false);
    //T组测试数据
    int T;
    cin >> T;
    while (T--) {
        //初始化为负无穷 -0x3f---> -1044266559
        memset(f, -0x3f, sizeof f);
        cin >> n;
        //总时长,背包容量
        int m = 0;
        //读入数据
        for (int i = 1; i <= n; i++) {
            cin >> q[i].s >> q[i].e >> q[i].l;
            m += q[i].s;
        }
        //贪心排序
        sort(q + 1, q + 1 + n, cmp);

        //01背包
        f[0] = 0;
        for (int i = 1; i <= n; i++)
            for (int j = m; j >= q[i].s; j--)
                f[j] = max(f[j], f[j - q[i].s] + max(0, q[i].e - (j - q[i].s) * q[i].l));

        //找出最大值
        int res = 0;
        for (int i = 1; i <= m; i++) res = max(res, f[i]);
        printf("Case #%d: %d\n", ++idx, res);
    }
    return 0;
}

三、 体积至少是v时的最小/大值

方法:\(f[0]\)初始化为\(0\),其余初始化为正/负无穷(分别对应题目问的最小/最大,代表不合法状态,不应该被选取),无需保证\(v\)大于等于\(0\)。
代表题目:\(AcWing\) \(1020\).潜水员,其代码如下:

#include <bits/stdc++.h>

using namespace std;
const int N = 1010; //气缸的个数上限
const int M = 100;  //需要的氧气、氮气上限值

int V1; //氧需要的量
int V2; //氮需要的量
int n;  //气缸的个数

int v1[N], v2[N], w[N];//第i个气缸里的氧和氮的容量及气缸重量
int f[M][M]; //DP数组

int main() {
    //读入氧需要的量,氮需要的量,气缸的个数
    cin >> V1 >> V2 >> n;
    //读入每个气缸的氧和氮的容量及气缸重量
    for (int i = 1; i <= n; i++) cin >> v1[i] >> v2[i] >> w[i];
    //求最小值要把除初始状态以外的所有状态初始化为+∞
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    //因为采用了降维,所以体积都是倒着的~
    for (int i = 1; i <= n; i++)
        for (int j = V1; j >= 0; j--)
            for (int k = V2; k >= 0; k--)
                f[j][k] = min(f[j][k], f[max(j - v1[i], 0)][max(k - v2[i], 0)] + w[i]);
    //输出
    printf("%d", f[V1][V2]);
    return 0;
}
上一篇:Vue3知识


下一篇:ASCIALL字符