强烈建议!!!读者先看一下这个8分钟的视频http:// https://v.douyin.com/iS93SYLv/,这位作者图形化解释更容易理解,我认为是讲的最清除的老师,本文对该内容适当进行了总结
什么是背包问题?
- 物品:有 n个物品,每个物品 i有一个重量 wi和一个价值 vi。
- 背包:有一个最大承重 W。
- 目标:选择一些物品放入背包,使得它们的总重量不超过 W,并且总价值最大。
- 即在背包容量有限的情况下选择的最优解使得背包所能容纳的物品总价值最高
01背包问题:每件物品只有一个,只能是选或者不选
解析:
共有n个物品,背包总承重为W,可以根据容量和物品确定一个状态,前i个物品,放在背包里,总重量不超过j的前提下,所获得的最大价值为dp[i][j]
在考虑第i个物品时,他有放或者不放两种选择,如果放,该物品需要满足j>=wi并且此时的最大价值大于它不放的情况
不放的情况:它的重量wi>j,放不下,或者能放下,但是放入之后,总价值并不会增大,因为可能别的物品重量更小而价值更高.
可以根据这个,写出状态方程
对所有j<ci :dp[i][j]=dp[i-1][j];//放不下该物品的情况,此时最大价值等于考虑第1到j-1个物品时,在容量为j时的值
对所有ci=<j<=W :dp[i][j]=max(dp[i-1][j],dp[i-1][j-ci]+wi)//放得下该物品的情况,最大价值等于不放的情况和放入之后情况下,两者的最大值
关键代码:
for(int i=1;i<=n;i++){//遍历n个物品
for(int j=0;j<=v;j++){//对每个物品,在0到v的最大容量情况下
if(j>=c[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-ci]+wi);
else dp[i][j]=dp[i-1][j]
}
}
01背包问题的优化
我们可以注意到,最大价值dp[i][j]只和上一行数组有关,因此我们可以把整个二维数组在空间上缩小为一行的滚动数组dp[],节省空间
关键代码
for(int i=1;i<=n;i++){
for(int j=v;j>=c[i];j--){
dp[j]=max(dp[j-c[i]]+w[i],dp[j]);
}
}
这有一个注意点,在对容量j进行遍历时,要从后往前遍历,因为我们要用到上一行数组的数据,如果从前往后遍历,后面的内容用的数据是已经更新的内容,而我们要的是前一行数组的内容,也就是没更新数组时的内容
例题:
#include<stdio.h>
int c[3001];
int v[3001];
int dp[10001];
int n,m;
int max(int a,int b){return a>b?a:b;}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d %d",&c[i],&v[i]);
}
for(int i=1;i<=n;i++){
for(int j=m;j>=c[i];j--){
dp[j]=max(dp[j-c[i]]+v[i],dp[j]);
}
}
printf("%d",dp[m]);
return 0;
}
多重背包问题
也就是物品的数量不只是1个,为多个,我们只需要多添加一个n[]数组,用于存储物品的数量,其实01背包问题就是多重背包问题的特殊情况
转移方程:dp[i][v]=max(dp[i][v],dp[i-1][v-k*ci]+k*w[i]),0=<k<=ni
核心代码:
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
for(int k=0;k<=n[i];k++){
if(j>=c[i]*k)dp[i][j]=max(dp[i-1][ [j-c[i]*k ]+w[i]*k,dp[i][j])
}
}
}
完全背包问题
物品的数量无限个,但因为背包容量有限,物品的最多数量也是有限制的,所有完全背包问题可以转化为多重背包问题,进而转变为01背包问题
核心代码:
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
for(int k=0;k*c[i]<=j;k++){
if(j>=c[i]*k)dp[i][j]=max(dp[i-1][ [ j-c[i]*k ]+w[i]*k,dp[i][j])
}
}
}
我们之前01背包问题优化时,有讲到从后往前遍历,可以避免后面要写入的数据本使用了更新后的数据,在这里,因为物品无限,我们可以从前往后遍历,就是用更新后的数据来更新接下来的结果,使那个"影响"不断叠加累计,相当于物品的无限
核心代码优化后:
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
if(j>=c[i])dp[i][j]=max(dp[i-1][j],dp[i][j-c[i]]+w[i]);//从前往后遍历,让他自动更新
else dp[i][j]=dp[i-1][j];
}
}
对它再用滚动数组进行空间上的优化后:
for(int i=1;i<=n;i++){
for(int j=c[i];j<=v;j++){
dp[j]=max(dp[ j-c[i] ]+w[i],dp[j]);
}
}