文章由以上两篇文章总结而来,相信能让读者理解透彻,不用再翻那么多博客反复学习 新人勿喷!
前提:一般情况下,优化用滚动数组就可以了,只是一维优化在完全背包问题中很有用。
滚动数组
原来空间复杂度:O(NV) 滚动数组优化后空间复杂度:O(2*N)
01背包
先考虑01背包基本思路如何实现
dp[i,j]:1~i个物品组成重量<=j的背包时,能获取的最大价值
状态转移方程:dp[i][j]=max(f[i−1][j],f[i−1][j−c[i]]+w[i])
可以看出,dp[i,j]只依赖与自己和i-1,而滚动数组就是把i给变成2,反复用0、1来进行dp,上一轮的值还是可以找到。
先给出代码:
for (int i = 1; i < N; i++) { // F[i,j]: 1~i个物品所能产生的最大价值
int ci = i % 2, i1 = ci ^ 1;
for (int j = 0; j <= V; j++) {
dp[ci][j]=dp[i1][j];
if (j >= w[i])
dp[ci][j] = max(dp[ci][j], F[i1][j - w[i]] + v[i]);
}
}
ci:目前行,i1:上一行 原理:1异或1=0 0异或1=1
i1永远跟ci不同,且正好是ci的上一行。
一维优化
原来空间复杂度:O(NV) 一维优化后空间复杂度:O(N)
01背包
先考虑01背包基本思路如何实现
dp[i,j]:1~i个物品组成重量<=j的背包时,能获取的最大价值
状态转移方程:dp[i][j]=max(f[i−1][j],f[i−1][j−c[i]]+w[i])
这个方程转化成一维,也要依赖 f[i−1][j] 和 f[i−1][j−c[i]] 这两个前项。那能否保证在推f[i][j]时,能够得到 f[i−1][j] 和 f[i−1][j−c[i]]的值呢?其实只需要把原来j从1~V改成从V~1就行了。
为啥呢?
状态转移方程:dp[j]=max(dp[j],dp[j-w[i]]+v[i])
这一轮循环为i,而我们要用上一轮(即i-1循环)的状态,就必须逆序,因为f(j)要用f(j-w[i])来推,而 j 一定是大于等于 j - w[i]的,也就是说,j一定在j-w[i]的上方,如下图 ,1~V就无法用上一轮更新的值了,而V~1就行,举个例子:
有两件物品:① 价格:2 重量: 1 ②价格:3 重量:1
第①轮,不管j是从1~V,还是从V~1,状态为:f(1)=2,f(2)=2
第②轮,如果是1~V,那状态为:f(1)=3,f(2)=6
明显不正确
如果是V~1,那状态为f(2)=5,f(1)=3.
为了深入理解,我画个图表示更新情况:
一句话总结,逆序才能让当前值能用上一轮(也就是i-1轮)推出的值,因为这样才能让上一轮推出的值在当前值的上方(如上图)
代码如下:
for (int i = 1; i <= n; i++)
for (int j = V; j >= 0; j--)
f[j] = max(f[j], f[j - c[i]] + w[i]);
拓展应用
Two Sets II - CSES 1093 - Virtual Judge (vjudge.net)https://vjudge.net/problem/CSES-1093
这是一道分割等和子集的经典题型,读者可以自己尝试做一下,也可以继续往下看。
这道题其实就是一个01背包,只是变成了方案数,限定了总和。
大家可以自行理解
且关于最后2逆元的推算,如下:
代码如下:
Source code - Virtual Judge (vjudge.net)https://vjudge.net/solution/43960514/LjLzcZ2Aj82rOymSaqP2
//01背包
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n;
using ll=long long;
ll dp[505][63000];
int main(){
cin>>n;
int sum=n*(n+1)/2;
if(sum%2==1){
cout<<0;
return 0;
}
dp[0][0]=1;
sum/=2;
for(int i=1;i<=n;i++){
for(int j=0;j<=sum;j++){
dp[i][j]=dp[i-1][j];
if(i<=j) (dp[i][j]+=dp[i-1][j-i])%=mod;
}
}
cout<<dp[n][sum]*(mod+1)/2%mod;
return 0;
}
之后我会慢慢发布多重背包、哈希、等等的算法详解。尽情期待。