T2,3,4,5,9动态背包问题

前言

本文主要介绍常见的四种背包问题,思维导图如下:

一、01背包

💡 现有 N 件物品和一个最多能承重 M 的背包,第 i 件物品的重量是 wi​,价值是 vi​。在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值。

因为每件物品只有选与不选两种状态,所以该问题又称01背包问题。

image-20240412130706125

题目链接:AcWing 2. 01背包问题

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int w[N], v[N];
int dp[N][N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (j < w[i]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
        }
    }

    cout << dp[n][m] << "n";

    return 0;
}

时间复杂度为O(nm)。

1.1 使用滚动数组优化

image-20240412130806763

image-20240412130834076

⚠️ 请牢记该式,后续讲解完全背包时会提到它。

这显然是错误的。事实上,让 j 从大到小遍历就不会出现这个问题。

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int w[N], v[N];
int dp[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    for (int i = 1; i <= n; i++)
        for (int j = m; j >= w[i]; j--)
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);

    cout << dp[m] << "n";

    return 0;
}

当然,w 数组和 v 数组也是不必要的,我们可以边输入边处理,因此可以得到01背包问题的最终版代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int dp[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;//n是物体数量,m是背包容积

    for (int i = 1; i <= n; i++) {
        int w, v;
        cin >> w >> v;//w: weight  v: value
        for (int j = m; j >= w; j--)
            dp[j] = max(dp[j], dp[j - w] + v);
    }

    cout << dp[m] ;

    return 0;
}

到此为止,可以总结出,当dp 数组是二维数组时, j 既可以从小到大遍历也可以从大到小遍历,但当 dp 数组是一维数组时, j只能从大到小遍历

二、完全背包

💡 现有 N 种物品和一个最多能承重 M 的背包,每种物品都有无限个,第 i 种物品的重量是 wi​,价值是 vi​。在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值。

image-20240412131025001

题目链接:AcWing 3. 完全背包问题

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int w[N], v[N];
int dp[N][N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            int t = j / w[i];
            for (int k = 0; k <= t; k++)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]);
        }

    cout << dp[n][m] << "n";

    return 0;
}

image-20240412131048173

2.1 使用滚动数组优化

image-20240412131207539

image-20240412131137792

根据1.1节中的结论,该式对应的是 j 从小到大遍历,于是我们只需把01背包问题的代码中的 j 改为从小到大遍历即可

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int dp[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        int w, v;
        cin >> w >> v;
        for (int j = w; j <= m; j++)  // 只需修改这一行
            dp[j] = max(dp[j], dp[j - w] + v);
    }

    cout << dp[m] << "n";

    return 0;
}

优化后的时间复杂度为 O ( n m ) O(nm) O(nm)。

三、多重背包

💡 现有 N 种物品和一个最多能承重 M 的背包,第 i 种物品的数量是 si​,重量是 wi​,价值是 vi​。在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值。

image-20240412131255569

题目链接:AcWing 4. 多重背包问题

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int dp[N][N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        int w, v, s;
        cin >> w >> v >> s;
        for (int j = 1; j <= m; j++) {
            int t = min(s, j / w);  // 只有这里需要修改
            for (int k = 0; k <= t; k++)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w] + k * v);
        }
    }

    cout << dp[n][m] << "n";

    return 0;
}

image-20240412131316312

3.1 使用二进制优化

image-20240412131338482

题目链接:AcWing 5. 多重背包问题 II

image-20240412131349381

#include <bits/stdc++.h>

using namespace std;

const int N = 11010, M = 2010;

int w[N], v[N];
int dp[M];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    int cnt = 0;
    while (n--) {
        int a, b, s;  // a是重量, b是价值, s是数量
        cin >> a >> b >> s;
        for (int k = 1; k <= s; k *= 2) {
            cnt++;
            w[cnt] = a * k, v[cnt] = b * k;
            s -= k;
        }
        if (s > 0) {
            cnt++;
            w[cnt] = a * s, v[cnt] = b * s;
        }
    }

    n = cnt;
    for (int i = 1; i <= n; i++)
        for (int j = m; j >= w[i]; j--)
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);

    cout << dp[m] << "n";

    return 0;
}

image-20240412131400957

四、分组背包

💡 现有 N 组物品和一个最多能承重 M 的背包,每组物品有若干个,同一组内的物品最多只能选一个。每件物品的重量是 wij,价值是vij,其中 i 是组号,j 是组内编号。在背包能承受的范围内,试问将哪些物品装入背包后可使总价值最大,求这个最大价值。

image-20240412131511242

题目链接:AcWing 9. 分组背包问题

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int w[N][N], v[N][N], s[N];
int dp[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> s[i];
        for (int j = 1; j <= s[i]; j++)
            cin >> w[i][j] >> v[i][j];
    }

    for (int i = 1; i <= n; i++)
        for (int j = m; j >= 1; j--)
            for (int k = 1; k <= s[i]; k++)
                if (j >= w[i][k])
                    dp[j] = max(dp[j], dp[j - w[i][k]] + v[i][k]);

    cout << dp[m] << "n";

    return 0;
}

总结

我们可以用一个公式来表示01背包、完全背包和多重背包:

image-20240412131429173

posted @ 2024-05-10 20:25  七龙猪  阅读(16)  评论(0)    收藏  举报
-->