You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.3 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

背包问题

一、01背包基础题

AcWing 2. 01背包问题

AcWing 423. 采药

AcWing 1024. 装箱问题

二维状态表示

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const int M = 1010;

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

int main() {
    cin >> m >> n;

    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            f[i][j] = f[i - 1][j]; // 不选
            if (j >= v[i])
                f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]); // 选
        }
    printf("%d\n", f[n][m]);
    return 0;
}

一维状态表示

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;

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

int main() {
    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]);

    printf("%d\n", f[m]);
    return 0;
}

二、二维费用01背包问题

AcWing 1022. 宠物小精灵之收服

AcWing 8. 二维费用的背包问题

#include <bits/stdc++.h>

using namespace std;

const int N = 110;   // 野生小精灵的数量
const int M1 = 1010; // 小智的精灵球数量
const int M2 = 510;  // 皮卡丘的体力值

int n, m1, m2;
int f[M1][M2]; // 一维:精灵球数量,二维:皮卡丘的体力值,值:抓到的小精灵数量最大值

int main() {
    cin >> m1 >> m2 >> n;
    m2--; // 留一滴血

    // 二维费用01背包
    // 降维需要将体积1、体积2倒序枚举
    for (int i = 1; i <= n; i++) {
        int v1, v2;
        cin >> v1 >> v2;
        for (int j = m1; j >= v1; j--)
            for (int k = m2; k >= v2; k--)
                f[j][k] = max(f[j][k], f[j - v1][k - v2] + 1); // 获利就是多了一个小精灵
    }
    // 最多收服多少个小精灵[在消耗精灵球、血极限的情况下,肯定抓的是最多的,这不废话吗]
    printf("%d ", f[m1][m2]);

    // 找到满足最大价值的所有状态里,第二维费用消耗最少的
    int cost = m2;
    for (int i = 0; i <= m2; i++) // 如果一个都不收服则体力消耗最少消耗值为0
        if (f[m1][i] == f[m1][m2])
            cost = min(cost, i);

    // 收服最多个小精灵时皮卡丘的剩余体力值最大是多少
    printf("%d\n", m2 + 1 - cost);
    return 0;
}

总结

  • 01背包,还是背一维的形式比较好,一来代码更短,二来空间更省,倒序就完了。
  • 二维费用的01背包,简化版本的01背包模板就有了用武之地,因为三维数组可能会爆内存。

三、01背包之恰好装满

AcWing 278. 数字组合

二维代码

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
const int M = 10010;
int n, m;
int v;
int f[N][M];

int main() {
    cin >> n >> m;

    for (int i = 0; i <= n; i++) f[i][0] = 1; // base case

    for (int i = 1; i <= n; i++) {
        cin >> v;
        for (int j = 1; j <= m; j++) {
            // 从前i-1个物品中选择装满j这么大的空间假设方案数是5个
            // 那么在前i个物品中选择装满j这么大的空间方案数最少也是5个
            // 如果第i个物品可以选择那么可能使得最终的选择方案数增加
            f[i][j] = f[i - 1][j];
            // 增加多少呢前序依赖是f[i - 1][j - v]
            if (j >= v) f[i][j] += f[i - 1][j - v];
        }
    }
    // 输出结果
    printf("%d\n", f[n][m]);
    return 0;
}

一维代码

#include <bits/stdc++.h>

using namespace std;
const int N = 10010;

int n, m;
int v;
int f[N]; // 在前i个物品体积是j的情况下恰好装满的方案数

int main() {
    cin >> n >> m;

    // 体积恰好j, f[0]=1, 其余是0
    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        cin >> v;
        for (int j = m; j >= v; j--)
            f[j] += f[j - v];
    }
    printf("%d\n", f[m]);
    return 0;
}

四、完全背包

AcWing 3. 完全背包问题

二维写法

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];

int main() {
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;
        for (int j = 1; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            if (j >= v)
                f[i][j] = max(f[i][j], f[i][j - v] + w);
        }
    }
    printf("%d\n", f[n][m]);
    return 0;
}

一维解法

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;
int n, m;
int f[N];

// 完全背包问题
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;
        for (int j = v; j <= m; j++)
            f[j] = max(f[j], f[j - v] + w);
    }
    printf("%d\n", f[m]);
    return 0;
}

五、完全背包之恰好装满

AcWing 1023. 买书

二维数组

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;
int v[5] = {0, 10, 20, 50, 100};
int f[5][N];

int main() {
    int m;
    cin >> m;
    // 前0种物品体积是0的情况下只有一种方案
    f[0][0] = 1;
    for (int i = 1; i <= 4; i++)
        for (int j = 0; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            if (v[i] <= j) f[i][j] += f[i][j - v[i]];
        }
    printf("%d\n", f[4][m]);
    return 0;
}

一维数组

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;
int v[5] = {0, 10, 20, 50, 100};
int f[N];

// 体积限制是恰好是因此需要初始化f[0][0]为合法解1其他位置为非法解0。
int main() {
    int m;
    cin >> m;
    // 前0种物品体积是0的情况下只有一种方案
    f[0] = 1;
    for (int i = 1; i <= 4; i++)
        for (int j = v[i]; j <= m; j++)
            f[j] += f[j - v[i]];
    // 输出
    printf("%d\n", f[m]);
    return 0;
}

总结 ① 完全背包的经典优化是哪个混蛋想出来的,真它娘的是个人才。 ② 对比01背包与完全背包的代码,发现是一正一反。 ③ 完全背包求最大值与恰好装满的方案数,除了初始化不同,其它的一样。f[i][0]=1这样的初始化,我也服了~ ④ 背包问题这样的经典代码,除了理解算法原理,会推导外,重点还是模板背诵。用模板知识解决实际问题才是考试的本质,虽然考试不一定能选拔出能力强的人才,但能选拔出做过这方面训练的人员。