提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
学习此篇之前先学习一下二维01背包问题
下面这个网址可以供大家学习,如果没看懂如何转换为一维的背包问题,可以看我下面的解释
代码随想录之01背包基础篇
提示:以下是本篇文章正文内容,下面案例可供参考
一、回顾二维dp
在二维中,我们当时用数组dp[i][j]
表示的含义为在物品前i个中,用j容量的背包,所装的最大价值为dp[i][j]
递推公式为 dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
我们从公式中可以看出 当前dp[i][j] 是由 上方dp[i-1][j] 和 左上方某个值dp[i-1][j-weight[i]]+当**前物品价值value[i]**组成,那么如何将二维dp转换为一维dp呢
二、一维dp
对于背包问题其实状态都是可以压缩的。
在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。
这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。
读到这里估计大家都忘了 dp[i][j]里的i和j表达的是什么了,i是物品,j是背包容量。
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
一定要时刻记住这里i和j的含义,要不然很容易看懵了。
动规五部曲分析如下:
确定dp数组的定义
在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
一维dp数组,其实就上上一层 dp[i-1] 这一层 拷贝的 dp[i]来。
所以在 上面递推公式的基础上去掉i这个维度就好。
递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
以下为分析:
dp[j]为 容量为j的背包所背的最大价值。
dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。
dp[j - weight[i]] + value[i] 表示 容量为 [j - 物品i重量] 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])
此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值
所以递归公式为:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
可以看出相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。
三、一维dp遍历顺序
对于递推公式dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
如何遍历呢?
这里我直接给结论,先遍历物品,再倒叙遍历背包
for(int i=1;i<=m;i++)
{
for(int j=n;j>=weight[i];j--)
{
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
大家会发现有两点细节
1.为什么背包容量j倒叙遍历?
2.为什么j到weight[i]就截止了?
样例中输入背包最大容量和物品数量
下面是物品重量与价值
大家仔细看,如果我们j从weight[i]开始到最大容量n结束,大家可以自己对照上图模拟一遍
初始化当i为0的时候,全部初始化为0
第一个物品从dp[2]开始遍历,为什么不从dp[1]开始遍历呢,因为dp[1]根本装不进第一个物品,我们直接不考虑,一维dp只考虑能装进的情况
dp[2]=max(dp[2],dp[2-weight[1]]+value[1])=max(0,dp[2-2]+1)=1 此时装进去了第一个物品
dp[3]=max(dp[3],dp[3-weight[1]]+value[1])=max(0,dp[3-2]+1)=1 此时装进去了第一个物品
但是请注意!!!!!!!!!!
dp[4]=max(dp[4],dp[4-weight[1]]+value[1])=max(0,dp[4-2]+1)=2 此时装进去了两次第一个物品
因为dp[2]已经装过一次了,导致dp[4]不为1
所以我们需要倒叙遍历
大家也可以这样理解,dp[j]的值是由上方的值和左侧的值推导出来,但左侧不能先出现值,不然会导致物品重复拿取
四、例题
点击这个例题进入网址
例题
#include<iostream>
using namespace std;
int dp[5005];//背包容量为i的最大价值背包为dp[i]
int m,n;
int weight[5005];
int value[5005];
int main()
{
cin>>m>>n;
for(int i=1;i<=m;i++)
{
cin>>weight[i];
}
for(int i=1;i<=m;i++)
{
cin>>value[i];
}
for(int i=1;i<=m;i++)
{
for(int j=n;j>=weight[i];j--)
{
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
cout<<dp[n];
return 0;
}
总结
可以看出,一维dp 的01背包,要比二维简洁的多! 初始化 和 遍历顺序相对简单了。
比较直观简洁,而且空间复杂度还降了一个数量级