完全背包问题简介
完全背包简单解释就是:
往一个固定体积的背包中放k种东西,每个东西放进背包的数量没有任何限制,每种东西放入背包都会有一定的奖励,求解如何放东西,才能使得获得的奖励总和最大。
题目链接
3. 完全背包问题 - AcWing题库https://www.acwing.com/problem/content/3/
解决思路
对于这类问题,解决方法就是典型的动态规划。
重要的点在于推导出公式。
对于这个背包而言,每次面对一种物品,其获得的奖励范围为:
不放,放1个,放2个,放3个 直到背包装不下。
所以我们可以得到
dp[n][m]=max(dp[n-1][m],dp[n-1][m-v[n]]+w[n],.....);
dp[i][j]的含义为取到第i个东西时,背包容量还剩 j 时,所获取到的最大的奖励值。
所以就有了以下代码。
①最朴素的求解
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
for(int t=0;t<=j/v[i];t++)
dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]*t]+w[i]*t);//这里是选出多个里面的最大值,注意比较大小的对象
}
}
②时间复杂度降低的改进
递归公式的推导改进 时间复杂度为n*v
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-v[i]*2]+w[i]*2 ...)
dp[i][j-v[i]]=max(dp[i-1][j-v[i]],dp[i-1][j-v[i]*2]+w[i] ...);
综合对比:
dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
if(j<v[i]) dp[i][j]=dp[i-1][j];
else{
dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]);//核心代码优化
}
}
}
③空间复杂度改进
#include<iostream>
#include<cstring>
using namespace std;
const int N=1005;
int dp[N][N];
int f[N];
int v[N],w[N];
int main(){
int n,k;//物品种数和背包体积
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
memset(dp,0,sizeof(dp));
//为了更加简化空间复杂度 如果不用输出具体的选择方案,只需要单纯的求取最大值 那么可以将数组转变为一维
//dp[i][j]的值仅仅与依赖i-1时的状态,因此,代码中无需使用二维的dp[i][j]进行存储,使用一维的f[j]进行存储,减少程序的空间。
//继续分析问题,因为dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]); f[j]应该从f[j-v[i],也就是比他小的数组值中获取,要与01背包区别开来
for(int i=1;i<=n;i++){
for(int j=v[i];j<=k;j++){//这个顺序必须是这样的原因见上
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[k];
//cout<<dp[n][k];
return 0;
}
一个变型题---可以用于练手
题目链接
900. 整数划分 - AcWing题库https://www.acwing.com/problem/content/902/
求解思路
这个问题最开始我陷入了思维误区,我一直在想怎么保证获得的数字划分一定是按照降序得到的。
后来查阅一些资料,提到了可以用完全背包的思路去求解,豁然开朗。
相当于我只要把体积为 0-n 的物品塞入容量为n的背包中,保证背包刚好被塞满,求解一共有多少种方法即可。不需要考虑顺序问题,只要在取之后,将数排下顺序即可。
求解代码
#include<iostream>
#include<cstring>
using namespace std;
const int N=1005;
const int mod=1e9+7;
int f[N]
int dp[N][N];//dp[i][j]代表的物理含义为:在前i个物品中取到总体积刚好满足容量的划分数
int main(){
int n;
cin>>n;
memset(dp,0,sizeof(dp));
for(int i=0;i<=n;i++){
dp[i][0]=1;
}
for(int i=1;i<=n;i++){
for(int j=0;j<=n;j++){
if(i>j) dp[i][j]=dp[i-1][j]%mod;//当前物品重量大于剩余容量,则该物品不能取了
else
dp[i][j]=(dp[i-1][j]+dp[i][j-i])%mod;
//dp[i][j]=dp[i-1][j]+dp[i][j-i]+dp[i][j-2*i]+... 等于取0个,取1个和取2个的和
//dp[i][j-i]=dp[i-1][j-i]+dp[i][j-2*i]+dp[i][j-3*i]+...
//dp[i][j]=dp[i-1][j]+dp[i][j-i];
}
}
//一维改进
f[0]=1;
/*for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
f[j]=(f[j-i]+f[j])%mod;
}
}*/
cout<<dp[n][n];
return 0;
}