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.
python/TangDou/AcWing/BeiBao/【总结】01背包与完全背包专题.md

8.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背包

1、状态定义

f[i][j] 代表在前i个物品中选择,背包容量上限是j的情况下,可以获得的最大价值。

2、状态转移

\large f[i][j] = max(f[i-1][j], f[i - 1][j - w] + v)

解释 我们带着一个剩余容量为j的背包来到第i个物品面前

  • ① 如果我们不选择第i个物品,那么我们的收益就只能在前i-1个物品中获取到,可以使用的空间没有变化,还是j,即可以从f[i-1][j]转移过来

  • ② 如果我们选择了第i个物品,那么就会要求在走完前i-1个物品时,剩余的空间是j-v,这样才能放得下第i个物品,即可以从f[i-1][j-w]+v获取到现在的最大价值

两个选择需要PK选一下大王,才是最终可能获取到的最大值,即

\large f[i][j]=max(f[i-1][j],f[i-1][j-w]+v)

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 w, v;
        cin >> w >> v;
        for (int j = 1; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            if (j >= w)
                f[i][j] = max(f[i][j], f[i - 1][j - w] + v); // 两种情况取最大值
        }
    }

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

4、实现代码一维

上面的二维实现中,我们通过瞪眼大法(观察法)得知,打表时每一行的数据,都只依赖于上一行,即f[i][?]一定是从f[i-1][?]转移过来,也就再向上的其它数据行变得没有用处,我们可以重复利用,这样就可以使用滚动数组或者降维处理。但由于后面的依赖于前面的,如果是正序枚举就会造成后面使用时,前面的数据已修改造成统计错误,办法就是从后向前枚举,以保证在后面的数据处理时,前面的数据还没有修改,是上一行的数据,就正确了。

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;

int n, m;
int f[N];

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

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

二、完全背包

1、状态定义

01背包

2、状态转移

i个物品,我们可以选择,也可以放弃,放弃了自然就是f[i1][j],这个没什么好讲。 如果我们选择了i物品,肯定要带上它的价值v,而承担它的重量w, 完成了选择它以后,我们接下来可以在前多少个物品中选呢?其实,还是i个,因为每种物品的数量无限啊!所以,选完了i,剩余的表示还是:f[i][jw]

\large f[i][j]=max(f[i-1][j],f[i][j-w]+v)

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

4、实现代码一维

再用二维降一维的思路来思考,就得到了完全背包的终极解法:

#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 w, v;
        cin >> w >> v;
        for (int j = w; j <= m; j++)
            f[j] = max(f[j], f[j - w] + v);
    }
    printf("%d\n", f[m]);
    return 0;
}

三、练习题

四、进阶【二维费用完全背包】

前导知识 AcWing 8. 二维费用的背包问题

#include <bits/stdc++.h>

// https://blog.csdn.net/gnn8291/article/details/90632100

using namespace std;
int W; // 可以提起 W单位重量的东西
int V; // 有一个能装V个单位体积的购物袋
int n; // 为商品种类数

// 注意多维限制的01背包一般N值都比较小100左右太大就会编译出错了。
const int N = 110;

// 某种商品的重量、体积和让利金额
int a[N], b[N], c[N];

// DP数组
int f[N][N][N];

/**
10 9 4
8 3 6
5 4 5
3 7 7
4 5 4

答案:
9
2 4
*/

int main() {
    // 输入
    cin >> W >> V >> n;
    // 每一种商品的重量、体积和让利金额
    for (int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> c[i];

    // 遍历每个种类
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= W; j++) {                       // 遍历重量
            for (int k = 1; k <= V; k++) {                   // 遍历体积
                int x = f[i - 1][j - a[i]][k - b[i]] + c[i]; // 如果选了结果是x
                int y = f[i - 1][j][k];                      // 如果不选结果是y
                // 如果剩余的重量和体积都够用的时候,尝试拿当前物品
                if (j >= a[i] && k >= b[i])
                    f[i][j][k] = max(x, y); // 也是两个分支,一是不选,另一个是选,要取最大值
                else
                    f[i][j][k] = y; // 装不下,不管是哪种原因装不下,都是这个分支
            }
        }
    }
    // 输出
    cout << f[n][W][V] << endl; // 小惠能够得到的最大让利金额 
    return 0;
}

带路径

#include <bits/stdc++.h>

using namespace std;
int W; // 可以提起 w 单位重量的东西,
int V; // 有一个能装v个单位体积的购物袋
int n; // 为商品种类数

// 注意多维限制的01背包一般N值都比较小100左右太大就会编译出错了。
const int N = 110;

// 某种商品的重量、体积和让利金额
int a[N], b[N], c[N];

// 在取得最大让利金额的时候,到底是拿了哪些商品?
string s[N][N][N];

// DP数组
int f[N][N][N];

/**
10 9 4
8 3 6
5 4 5
3 7 7
4 5 4

答案:
9
2 4
*/

int main() {
    // 输入
    cin >> W >> V >> n;

    // 每一种商品的重量、体积和让利金额
    for (int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> c[i];

    // 遍历每个种类
    for (int i = 1; i <= n; i++) {
        // 遍历重量(填表)
        for (int j = 1; j <= W; j++) {
            // 遍历体积(填表)
            for (int k = 1; k <= V; k++) {
                int x = f[i - 1][j - a[i]][k - b[i]] + c[i];
                int y = f[i - 1][j][k];

                // 如果剩余的重量和体积都够用的时候,尝试拿当前物品
                if (j >= a[i] && k >= b[i]) {
                    // 也是两个分支,一是不选,另一个是选,要取最大值
                    if (x > y) { // 要上合适
                        f[i][j][k] = x;
                        // 同时记录拿了这个物品
                        s[i][j][k] = s[i - 1][j - a[i]][k - b[i]] + " " + (char)(i + '0');
                    } else // 不要合适
                        f[i][j][k] = y;
                } else { // 装不下,不管是哪种原因装不下,都是这个分支
                    f[i][j][k] = y;
                    // 同时记录不拿当前物品,保持和之前一样的物品列表
                    s[i][j][k] = s[i - 1][j][k];
                }
            }
        }
    }
    // 输出
    cout << f[n][W][V] << endl; // 小惠能够得到的最大让利金额
    string str = s[n][W][V];    // 依次为从小到大排列的商品序号
    cout << str.substr(1, str.size() - 1);
    return 0;
}