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]个,那么我当然可以取从1l[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     }
复制代码

后续的多维背包也是一样的思路(暂且略之)

posted @   Conqueror712  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示