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 @   paopaotangzu  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示