chapter12-2-背包问题

动态规划最经典并且在机试中重点考查的问题——背包问题。背包问题的变体繁多,这里主要讨论3种。

1.0-1背包

0-1背包问题描述的是,有\(n\)件物品,每件物品的重量为\(w[i]\),其价值为\(v[i]\),现在有容量为\(m\)的背包,如何选择物品使得装入背包物品的价值最大。

首先介绍求解这个问题的动态规划方法,其时间复杂度为\(O(nm)\),空间复杂度也为\(O(nm)\)

设置一个二维数组\(dp[ ][ ]\),令\(dp[i][j]\)表示前\(i\)个物品装进容量为\(j\)的背包能获得的最大价值。那么\(dp[n][m]\)的值就是0-1背包问题的解。

0-1背包的递推式如下:
0-1背包递推式.jpg
0-1背包.jpg

点菜问题
//0-1背包问题 点菜问题 2024-03-17
#include <iostream>
#include <cstdio>

using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1000 + 10;

int weight[MAXN];
int value[MAXN];

int dp[MAXN][MAXM];


int main() {
    int c, n;//容量为c,物品共n件
    while(scanf("%d%d", &c, &n) != EOF) {
        for(int i = 1; i <= n; ++i) {
            scanf("%d%d", &weight[i], &value[i]);
        }

        for(int i = 0; i <= n ; ++i) {
            dp[i][0] = 0;
        }
        for(int i = 0; i <= c; ++i) {
            dp[0][i] = 0;
        }
        //考虑第i件物品放入容量为j的背包
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= c; ++j) {
                if(j < weight[i]) {
                    dp[i][j] = dp[i -1][j];
                } else {
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i]);
                }
            }
        }
        //查表
        printf("%d\n",dp[n][c]);
    }
    return 0;
}

优化T__.jpg

优化空间复杂度-dp设为一维数组
//0-1背包问题 点菜问题-优化空间复杂度 2024-03-17
#include <iostream>
#include <cstdio>

using namespace std;
const int MAXN = 100 + 10;
const int MAXM = 1000 + 10;

int weight[MAXN];
int value[MAXN];

int dp[MAXM];


int main() {
    int c, n;//容量为c,物品共n件
    while(scanf("%d%d", &c, &n) != EOF) {
        for(int i = 1; i <= n; ++i) {
            scanf("%d%d", &weight[i], &value[i]);
        }

        for(int j = 0; j <= c; ++j) {
            dp[j] = 0;
        }
        //考虑第i件物品放入容量为j的背包
        for(int i = 1; i <= n; ++i) {
            for(int j = c; j >= weight[i]; --j) {
                dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        //查表
        printf("%d\n",dp[c]);
    }
    return 0;
}

FYI:一般采用下标为1开始存储weight和value,因为我们规定的是下标为i的话是前i件物品,那么默认下标为0的时候是什么物品都没有,是一个空集,这样可以方便我们更加好的进行处理。

2.完全背包

完全背包问题和0-1背包问题很像,二者唯一的区别就在于完全背包里面,同一件物品有无穷多件。有\(n\)件物品,每件物品的重量为\(w[i]\),其价值为\(v[i]\),每种物品的数量均为无限个,现在有容量为\(m\)的背包,如何选择物品使得装入背包物品的价值最大?

同样设置一个二维数组\(dp[MAXN][MAXM]\),令\(dp[i][j]\)表示前i个物品装进容量为j的背包能够获得的最大价值。数组\(dp[n][m]\)即为完全背包问题的解。

和0-1背包问题一样,只考虑第i件物品时,可将情况分为是否放入第i件物品两种:

完全背包.jpg

HDU 4508_减肥记
//完全背包 2024-03-17
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 100 + 10;
const int MAXM = 1e5 + 10;

int weight[MAXN];
int value[MAXN];

int dp[MAXN][MAXM];

int main() {
    int n, m;
    while(scanf("%d", &n) != EOF) {
        for(int i = 1; i <= n; ++i) {
            scanf("%d%d", &value[i], &weight[i]);
        }
        scanf("%d", &m);

        for(int i = 0; i <= n; ++i) {
            dp[i][0] = 0;
        }
        for(int j = 0; j <= m; ++j) {
            dp[0][j] = 0;
        }
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= m; ++j) {
                if(j < weight[i]) {
                    dp[i][j] = dp[i - 1][j]; 
                } else {
                    dp[i][j] = max(dp[i-1][j], dp[i][j - weight[i]] + value[i]);
                }
            }
        }
        printf("%d\n",dp[n][m]);
    }
    return 0;
}

优化空间复杂度,请看这张图修改dp数组和递推式(也可以说是状态转移)。
完全背包优化.jpg

总之,完全背包问题的特点是每类物品可选的数量为无穷,其解法与0-1背包问题整体保持一致,与其不同的仅为状态更新时的遍历顺序。

3.多重背包

每件物品既不是只有1件,又不是无限多件,而是有限个。多重背包问题:有\(n\)种物品,每种物品的重量为\(w[i]\),其价值为\(v[i]\),每种物品的数量为\(k[i]\),现在有个容量为\(m\)的背包,如何选择使得装入背包物品的价值最大。

求解多重背包的方法就是直接转化为0-1背包问题,不过(1)简单粗暴,把有多个同一物品的每一个都看成是相同重量、相同价值的不同物品;(2)是对同种物品进行有技巧地拆分和捆绑,得到不同的组合,使得可以任意想获得几件此个物品,都能用捆绑的组合加一加得到

多重背包-1.jpg
多重背包-2.jpg

注意这个技巧性的拆分:就是类似二进制法拆,最后不满足2的幂次倍的单独捆绑为一个新物品。

HDU 2191
/*
* 题目名称:珍惜现在,感恩生活
* 题目来源:HDU 2191
* 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2191
* 代码作者:杨泽邦(炉灰)
*/

#include <iostream>
#include <cstdio>
#include <climits>

using namespace std;

const int MAXN = 100 + 10;
const int MAXM = 1e4 + 10;

int dp[MAXM];
int value[MAXN];                        //物品价值
int weight[MAXN];                       //物品重量
int amount[MAXN];                       //物品数目
int newValue[20 * MAXN];                //拆分后物品价值
int newWeight[20 * MAXN];               //拆分后物品重量

int main() {
    int caseNumber;
    scanf("%d", &caseNumber);
    while (caseNumber--) {
        int n, m;
        scanf("%d%d", &m, &n);          //n件物品,m容量的背包
        int number = 0;                 //分解后物品数量
        for (int i = 0; i < n; ++i) {
            scanf("%d%d%d", &weight[i], &value[i], &amount[i]);
            for (int j = 1; j <= amount[i]; j <<= 1) {
                newValue[number] = j * value[i];
                newWeight[number] = j * weight[i];
                number++;
                amount[i] -= j;
            }
            if (amount[i] > 0) {
                newValue[number] = amount[i] * value[i];
                newWeight[number] = amount[i] * weight[i];
                number++;
            }
        }
        for (int i = 0; i <= m; ++i) {
            dp[i] = 0;                  //初始化
        }
        for (int i = 0; i < number; ++i) {
            for (int j = m; j >= newWeight[i]; --j) {
                dp[j] = max(dp[j], dp[j - newWeight[i]] + newValue[i]);
            }
        }
        printf("%d\n", dp[m]);
    }
    return 0;
}

不知道咋,我代码AC不了,感觉逻辑没问题,拆分再重新捆绑,记录新物品的数量,眼睛快瞎了,先这样吧。

posted @ 2024-03-17 21:57  paopaotangzu  阅读(7)  评论(0编辑  收藏  举报