01背包空间优化
当初学01背包的时候,会有这样一个状态转移方程:
约定:
dp[i][j]表示在0~i下标物品中选取,在总金额不超过j的情况下获得的最大价值
v[i]表示第 i 下标的物品的价值
dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]]+v[i]);
dp[i-1][j]
对应不选 【第 i 下标】 的物品
dp[i-1][j-v[i]]+v[i]
对应选 【第 i 下标】 的物品
可以很快写出递推的式子
maxPrice表示考虑的最大总金额
共有 0~n-1 下标共n件物品
for(int i=0; i<n; i++)
for(int j=0; j<=maxPrice; j++)
dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]]+v[i]);
可以发现,当前格子的答案,只和上一行0~j下标的格子
有关
那么我们只存一行就好了啊,而且问题来自于上一行 j 以及前面的区域,我们必须让 j 以递减的形式更新,以保证能够取到上一行的前面的值(因为 j 递减更新的话前面是旧值,我们恰恰需要上一行的旧值)
如图:蓝色表现新更新的值,红色表示上一行的旧值,即考虑下标 0~i-1
的情况
于是可以在空间上简化状态转移
for(int i=0; i<n; i++)
for(int j=maxPrice; j>=0; j--)
dp[j] = max(dp[j], dp[j-v[i]]+v[i]);
完全背包时间优化
完全背包和01背包类似,但是每件物品可以选无数次,那么还是可以基于01背包的基础上快速写出状态转移
约定:
dp[i][j]表示在0~i下标物品中选取,在总金额不超过j的情况下获得的最大价值
共有 0~n-1 下标共n件物品
maxPrice表示考虑的最大总金额
v[i]表示第 i 下标的物品的价值
k表示第i下标的物品,选取k件
for(int i=0; i<n; i++)
for(int j=0; j<=maxPrice; j++)
for(int k=0; j-k*v[i]>=0; k++)
dp[i][j] = max(dp[i-1][j], dp[i-1][j-k*v[i]]+k*v[i]);
枚举第i下标物品选取的可能,然后做多次01背包取最大结果,可是这样带来的是时间的开销增加,有没有办法去掉k的循环?
【在0~i
下标物品中选取,在总金额不超过 j 的情况下获得的最大价值】,其实问题可以这么想:
同一件物品,我们可以取无数次,那么问题转化为:
- 【在
0~i-1
下标物品中选取,在总金额不超过 j 的情况下获得的最大价值】 - 【在
0~i
下标物品中选取,在总金额不超过 j-v[i] 的情况下获得的最大价值】,即尝试多选一件
问题1是上一行的值,没什么问题,问题2,是这一行右边的值,因为 j 是递增更新的,我们很快发现,问题2的最优答案我们刚刚已经做过了,就在这一行的左边,那么我们很快能够做出优化
假设问题1的解是红色区域,问题2的解是黄色区域
约定:
dp[i][j]表示在0~i下标物品中选取,在总金额不超过j的情况下获得的最大价值
共有 0~n-1 下标共n件物品
maxPrice表示考虑的最大总金额
v[i]表示第 i 下标的物品的价值
for(int i=0; i<n; i++)
for(int j=0; j<=maxPrice; j++)
dp[i][j] = max(dp[i-1][j], dp[i][j-v[i]]+v[i]);
如何枚举k?即如何确保选取多件物品?
dp[i][j-v[i]]
的值,由dp数组的定义,已经是考虑选取多件 i 物品的结果了,而我们要做的就是试图增加一件 i 物品,基于已经最优的答案(可能选了k件),尝试增加一件 i 物品,看看是否会得到更大的结果
同样可以基于01背包的空间优化,这里给出空间优化的代码,而且这个代码和01背包的空间优化非常相似,只是 j 循环的方向发送改变
for(int i=0; i<n; i++)
for(int j=0; j<=maxPrice; j++)
dp[j] = max(dp[j], dp[j-v[i]]+v[i]);