dp_01背包与完全背包
问题
完全背包
有n个物品
已经第i个物品重量wi 价值vi 无数个
总背包容量 W
01背包
有n个物品
已经第i个物品重量wi 价值vi 只有1个
总背包容量 W
思路:
设dp[i][j] i为考虑前i个物品时 j为背包总量。
则有
无后效性:i为前i个物品考虑, j为总量,两者均从小到大增加,i只需要跟i-1有关系。j并不是与j-1有关系,而是j与j+v[i] 有关系。
子问题重叠:使用dp[i-1][<j] 总满足
最优子结构:
- 01背包: dp[i][j+w[i]] = max(dp[i-1][j+w[i]],dp[i-1][j]+v[i]);//在选不选择i 中取最大值
- full背包: dp[i][j+w[i]] = max(dp[i-1][j+w[i]],dp[i][j]+v[i]);//在选择 0~n个i 中取最大值
但如果直接用二维,有些题会卡MLE 所以必须要用滚动数组更新
在最优子结构那里,对比01与full,不同点在于,dp[i-1][j]+v[i] 与 dp[i][j]+v[i]
这句在逻辑上的不同是:01只能选与不选 full则你可以选多少次都可以。
dp[i-1][j]+v[i] 为: 考虑前i-1个物品时 肯定加上i物品时
dp[i][j]+v[i] 为: 考虑前i个物品时 肯定加上i物品时(就算是已经考虑了i,也再加一次v[i])
滚动数组后,把i干掉。i的后效性不影响。
跟 https://www.cnblogs.com/kingbuffalo/p/16228249.html 类似,因为只有一维数组,所以是需要考虑其遍历顺序的。
j要注意是:
01的dp下标影响是:dp[j+w[i]] 只能让 dp[j]+v[i] 的值影响一次。
按照从小到大的顺序,若令j=w[i] 则 dp[2*w[i]] 会进行有一次 将 v[i] * 2 的一次max对比。
故需要从大到小的顺序
而full的dp下标影响是:dp[j+w[i]] 只能让 dp[j]+v[i] 的值影响多次。
这时,就必须从小到大的顺序了。
练习题 AC代码
01背包 附 poj 3624 ac代码
#define MMAX 13000
int dp[MMAX];
int main(){
int N,W;
int wi,di;
scanf("%d%d",&N,&W);
for( int i=0;i<N;++i){
scanf("%d%d",&wi,&di);
for( int w=W;w>=wi;w--){
dp[w] = max(dp[w],dp[w-wi]+di);
}
}
printf("%d\n",dp[W]);
return 0;
}
完全背包 附 poj1384 ac代码
#define INF 0x3f3f3f3f
#define NMAX 10005
int dp[NMAX];
int main(){
int T;
int E,F;
int W;
int N;
int pi,wi;
scanf("%d",&T);
while(T--){
scanf("%d%d",&E,&F);
W = F-E;
memset(dp,0x3f,sizeof(dp));
dp[0] = 0;
scanf("%d",&N);
for(int i=0;i<N;++i){
scanf("%d%d",&pi,&wi);
for( int j=wi;j<=W;++j){
dp[j] = min(dp[j],dp[j-wi]+pi);
}
}
if ( dp[W] == INF ){
printf("This is impossible.\n");
}else{
printf("The minimum amount of money in the piggy-bank is %d.\n",dp[W]);
}
}
return 0;
}
变种题
- 01背包求方案数 https://www.acwing.com/problem/content/description/280/
解法:将 dp[w] += dp[w-wi]; dp[0]=1 倒序即可。其中dp[0]=1是指:0时也是方案的一种。 - 完全背包求方案数 https://www.acwing.com/problem/content/281/
解法:将 dp[w] += dp[w-wi]; dp[0]=1 顺序即可。其中dp[0]=1是指:0时也是方案的一种。