chapter12-2-背包问题
动态规划最经典并且在机试中重点考查的问题——背包问题。背包问题的变体繁多,这里主要讨论3种。
1.0-1背包
0-1背包问题描述的是,有
首先介绍求解这个问题的动态规划方法,其时间复杂度为
设置一个二维数组
0-1背包的递推式如下:
点菜问题
//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; }
优化空间复杂度-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背包问题很像,二者唯一的区别就在于完全背包里面,同一件物品有无穷多件。有
同样设置一个二维数组
和0-1背包问题一样,只考虑第i件物品时,可将情况分为是否放入第i件物品两种:
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数组和递推式(也可以说是状态转移)。
总之,完全背包问题的特点是每类物品可选的数量为无穷,其解法与0-1背包问题整体保持一致,与其不同的仅为状态更新时的遍历顺序。
3.多重背包
每件物品既不是只有1件,又不是无限多件,而是有限个。多重背包问题:有
求解多重背包的方法就是直接转化为0-1背包问题,不过(1)简单粗暴,把有多个同一物品的每一个都看成是相同重量、相同价值的不同物品;(2)是对同种物品进行有技巧地拆分和捆绑,得到不同的组合,使得可以任意想获得几件此个物品,都能用捆绑的组合加一加得到。
注意这个技巧性的拆分:就是类似二进制法拆,最后不满足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不了,感觉逻辑没问题,拆分再重新捆绑,记录新物品的数量,眼睛快瞎了,先这样吧。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探