几类背包问题
1.0/1背包
顾名思义,既是指一个背包,指定一些物品质量(体积)与价值(每种物品只有一个),求出可装的最大价值。每种物品有0(不选)、1(选)两种状态。
而基本思路为dp:
已知条件有第n个物品的重量w[i],价值c[i],以及背包的总容量v
简单分析
假设当前已经处理好了前i-1个物品的所有状态,那么对于第i个物品,当其不放入背包时,背包的剩余容量不变,背包中物品的总价值也不变,故这种情况的最大价值为dp[i-1][j];当其放入背包时,背包的剩余容量会减小,背包中物品的总价值会增大 ,故这种情况的最大价值为dp[i-1][j-w[i]]+c[i]
由此可以得出状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i])
代码如下:
#include<cstdio>
#include<algorithm>
using namespace std;
int n,w[100005],c[100005],v,dp[5005][5005];
int main(){
scanf("%d %d",&v,&n);
for(int i=1;i<=n;i++)scanf("%d %d",&w[i],&c[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=v;j++){
if(j>=w[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);
else dp[i][j]=dp[i-1][j];
}
}
printf("%d",dp[n][v]);
return 0;
}
But,这个代码当遇见n或v较大时便会RE,强行改变dp数组大小有会MLE,使用map的话又很有可能TLE
于是乎,就有了降维思想!!!
通过观察以上代码,不难发现第一维枚举i时只用到了i,i-1,即为更新前与更新后,那么这一维就可以省略(只需覆盖即可),再通俗地说就是只需要重量的第二维。
新的代码:
#include<iostream>
using namespace std;
const int M_AX=1e5+7;
int n,m,w[M_AX],v[M_AX],f[M_AX];
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
for(int i=1;i<=n;i++){
for(int l=m;l>=w[i];l--){
if(f[l-w[i]]+v[i]>f[l])f[l]=f[l-w[i]]+v[i];
}
}
cout<<f[m];
return 0;
}
完全背包
与0/1背包问题相反,完全背包在0/1背包选与不选基础上多了一个条件——每个物品都有无数个,于是就有了两种思路……
思路一:
第三层循环枚举第i个物品的选择数量。
代码在0/1背包上进行一些简单改动,在此不进行展示。
f
i
,
j
=
max
k
+
∞
{
f
i
−
1
,
j
−
k
⋅
w
i
+
k
⋅
c
i
}
f_{i,j}=\max\limits^{+\infin}_k\{f_{i-1,j-k\cdot w_i}+k\cdot c_i\}
fi,j=kmax+∞{fi−1,j−k⋅wi+k⋅ci}
思路二:
对方法一进行优化,省去第三层循环,状态转移方程其实与0/1背包相同,
只不过第二层循环顺序发生了改变。
对于0/1背包:for(int j=m;j>=w[i];j–)
而对于完全背包:for(int j=w[i];j<=m;j++)
与0/1背包相同,我们可以将第一维去掉来优化空间复杂度。如果理解了0/1背包的优化方式,就不难明白压缩后的循环是正向的
做一个简单的代码展示
#include<cstdio>
using namespace std;
int n,m,dp[1005],w[1005],c[1005];
int main(){
scanf("%d %d",&m,&n);
for(int i=1;i<=n;i++)scanf("%d %d",&w[i],&c[i]);
for(int i=1;i<=n;i++){
for(int j=w[i];j<=m;j++){
if(dp[j-w[i]]+c[i]>dp[j])dp[j]=dp[j-w[i]]+c[i];
}
}
printf("%d",dp[m]);
return 0;
}