Backpack_Problem背包问题
背包问题入门篇
首先当然是我们最熟悉的01背包啦!
最简单的想法是,二维dp数组,不考虑其他的优化。
状态:
dp[i][j]
表示取第 i
个物品,总体积为 j
时候的情况。
那么我们就会获得两种转移情况,对于下一个物品。一种是取了,一种是没取。
于是我们获得了状态转移方程:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]] + w[i]);
这里要说明的是,01背包大多数都是对 j 倒序枚举,而完全背包则是正序枚举。
至于为什么是这种顺序或者那种顺序,只需要看状态转移是怎么转移的就好啦。
实在不行可以画个图帮忙理解。
当然,这里既然用的是二维dp数组,那就无需倒序啦。
部分代码展示如下:
1 for (int i = 1;i <= n;i++){
2 for (int j = 0;j <= m;j++){
3 if (v[i] > j){
4 dp[i][j] = dp[i-1][j];
5 }
6 else{
7 dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]] + w[i]);
8 }
9 }
10 }
——————————————————我是华丽的分割线——————————————————
有了01背包,怎么能少了完全背包呢!
基于上题,这里就不多做赘述,直接上关键部分(一维dp数组优化)。
状态:
dp[j]
表示总体积为 j
时候的情况。
状态转移方程:
dp[j] = max(dp[j],dp[j-v[i]] + w[i]);
部分代码展示如下:
1 for (int i = 1;i <= n;i++){
2 for (int j = v[i];j <= m;j++){
3 dp[j] = max(dp[j],dp[j-v[i]] + w[i]);
4 }
5 }
——————————————————我是华丽的分割线——————————————————
接下来要登场的是大名鼎鼎的多重背包!
顾名思义,每种物品可以取多个(有限)。
状态:
dp[j]
表示总体积为 j
时候的情况。
状态转移方程:
dp[j] = max(dp[j],dp[j-v[i]] + w[i]);
什么嘛,这跟前几个的状态转移方程有什么区别嘛!这不是唬人呢嘛!
其实,多重背包的重点在于for循环
的不同。
对于数据量比较小的情况,我们直接暴力三重for
就行了,代码如下:
1 for (int i = 1;i <= n;i++){
2 for (int k = 1;k <= l[i];k++){
3 for (int j = m;j >= v[i];j--){
4 dp[j] = max(dp[j],dp[j-v[i]] + w[i]);
5 }
6 }
7 }
解释一下:
第一重 i
的含义不变,枚举第 i
种物品的情况;
第二重 k
的含义是 我这一种(第 i
种)物品一共有l[i]
个,那么我当然可以取从1
到l[i]
个该种物品啦!
第三重 j
的含义就很简单啦,首先我们看到这是逆序遍历的,是因为这其实相当于一个01背包,只不过把l[i]
次展开了而已,
又因为我们用的是一维dp数组,故采用逆序遍历的方法。这里的下界的意思是,
如果这个物品的体积比j
还大,那么显然不用考虑了,直接continue就可以了。
当然,对于数据量较大(真的很大)的多重背包我们就不能这么写了(除非你有量子计算机之类的bushi)。
——————————————————我是华丽的分割线——————————————————
那么应该如何写呢,我们来看。
话不多说,先上代码:
1 for (int i = 1;i <= n;i++){
2 int res = l[i];
3 for (int k = 1;k <= res; res -= k, k *= 2){
4 for (int j = m;j >= v[i] * k;j--){
5 dp[j] = max(dp[j],dp[j-v[i]*k] + w[i]*k);
6 }
7 }
8 for (int j = m; j >= v[i] * res;j--){
9 dp[j] = max(dp[j],dp[j-v[i]*res] + w[i] * res);
10 }
11 }
可以看到,我们在每次枚举i
的时候,都用一个res
把该种物品的个数记下来。
对于每一次枚举k
,我们让res -= k && k *= 2
也就是说,这里res
就记录的是剩余的数量(因为把k
个都拿走了)。
至于这里为什么让k *= 2
,牵扯到一个数学小知识:
1,2,...,2m中选一些数字相加,可以得出任意的∈[ 0 , 2m+1 )的值,且每个数字只能用一次。
用数学归纳法不难证明。
这样一来,我们的第二重循环的复杂度就由n
化到了logn
,与此同时,j
也随之变化。
——————————————————我是华丽的分割线——————————————————
此外,还有一种基于单调队列的多重背包实现方法,这里暂时挖个坑,以后可能会添加进来()。
——————————————————我是华丽的分割线——————————————————
下面是分组背包。懒了,直接上代码:
1 for (int i = 1;i <= 1000;i++){
2 for (int j = m;j >= 0;j--){
3 for (auto k: c[i]){ //vector <int> c;
4 if (v[k] <= j)
5 dp[j] = max(dp[j],dp[j-v[k]]+w[k]);
6 }
7 }
8 }
——————————————————我是华丽的分割线——————————————————
以及二维背包。偷懒++:
1 for (int i = 1;i <= n;i++){
2 for (int j = m;j >= v[i];j--){
3 for (int x = k;x >= t[i];x--){
4 dp[j][x] = max(dp[j][x],dp[j-v[i]][x-t[i]] + w[i]);
5 }
6 }
7 }
后续的多维背包也是一样的思路(暂且略之)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)