1.背包问题
题目:
给定n种物品,第i个物品的体积是ci,价值是wi,背包的容量为C。把物品装入背包时,我们可以选择装或不装。如何选择装入背包的物品,使装入背包中物品的总价值最大。
(N<=1000,C<=1000)
1.DP状态的设计
引入一个(N+1)×(C+1)的二维数组dp[][],称为DP状态,dp[i][j]表示把前i个物品(从第一个到第i个)装入容量为j的背包中获得的最大价值。可以把每个dp[i][j]都看作一个背包:背包的容量为j,装1-i这些物品。最后dp[N][C]就是问题的答案——将N个物品装进容量为C的背包。
2.DP转移方程
自底向上的方法计算,分成两种情况:
1).第 i 个物品的体积比容积 j 还大,不能装进容量为 j 的背包。那么直接继承前 i -1 个物品装进容量j的背包即可,即dp[i][j]=dp[i-1][j];
2).第 i 个物品的体积比容量 j 小,能装进背包。又可以分为两种情况:装或不装第 i 个物品。
(1)装第 i 个物品。从前n-1个物品的情况中推广开,前 i -1个物品的价值为dp[i-1][j]。第i个物品装进背包后容积减少了c[i],价值增加了w[i],有dp[i][j]=dp[i-1][j-c[i]]+w[i]。
(2)不装第 i 个物品,有dp[i][j]=dp[i-1][j]
取两种情况中的最大值,状态转移方程为
dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);
时间复杂度为O(NC),空间复杂度为O(NC);
递推代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1011;
int w[N],c[N];//物品的价值和体积
int dp[N][N];
//dp[i][j]:代表放入了第i个物品,总的空间有j这么大
int solve(int n,int C){
for(int i=1;i<=n;i++){
for(int j=0;j<=C;j++){
//如果当前这个物品比这个背包的体积大
if(c[i]>j){
dp[i][j]=dp[i-1][j];
}
//这个物品能放到当前状态的背包里
else{
//比较到底是放这个物品的价值大还是不放这个物品的价值大?
//dp[i-1][j]:不放这个物品
//dp[i-1][j-c[i]]+w[i]:放这个物品
dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);
}
}
}
return dp[n][C];
}
int main(){
int T;cin>>T;
while(T--){
int n,C;
cin>>n>>C;
for(int i=1;i<=n;i++) cin>>w[i];//价值
for(int i=1;i<=n;i++) cin>>c[i];//体积
memset(dp,0,sizeof(dp));
cout<<solve(n,C)<<endl;
}
return 0;
}
记忆化代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1011;
int w[N],c[N];//物品的价值和体积
int dp[N][N];
int solve(int i,int j){//前i个物品,放进容量j的背包
if(dp[i][j]!=0) return dp[i][j];//记忆化
if(i==0) return 0;
int res;
if(c[i]>j) res=solve(i-1,j);//第i个物品比背包还大,装不了
else res=max(solve(i-1,j),solve(i-1,j-c[i])+w[i]);//第i个物品可以装得下
return dp[i][j];
}
int main(){
int T;cin>>T;//T组测试数据
while(T--){
int n,C;//物品个数,背包体积
cin>>n>>C;
for(int i=1;i<=n;i++) cin>>w[i];//价值
for(int i=1;i<=n;i++) cin>>c[i];//体积
memset(dp,0,sizeof(dp));
cout<<solve(n,C)<<endl;
}
return 0;
}
但是我们不难发现在上述代码中我们每次只用到了当前行和上一行的数据,例如我们现在在装第i种物品,那么我只需要用到第i-1种物品的数据,那么我们有没有什么办法能够优化我们的空间复杂度呢? 要知道,我们在做题时,很多情况下创建二维数据会导致空间超载。