零一背包
有 N 件物品和一个容量为 V 的背包.放入第 i 件物品耗用的费用为Ci(即所占用背包的体积),得到的价值是 Wi.求将哪些物品装入背包所得到的总价值最大.
零一就是每种物品只有一个,只考虑装或者不装。
定义状态:dp[i][j]:前i件物品放到一个容量为j(1<=j<=V)的背包中可以获得的最大价值
转移方程:
如果j>=Ci, 第i个物品能装进去,那么可以选择装或者不装;
不装的话,最大价值就是 前i-1件物品放到一个容量为j的背包中可以获得的最大价值;
装的话,最大价值就是 前i-1件物品放到一个容量为j-Ci的背包中可以获得的最大价值+Wi;
也就是能从两个状态进行转移:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-C[i]]+W[i])
反之 第i个物品装不进去,那不装呗,最大价值就是 前i-1件物品放到一个容量为j的背包中可以获得的最大价值
dp[i][j]=dp[i-1][j];
打个表格来看一下:假设背包容量为10
物品体积和价值为:
体积 价值 0 1 2 3 4 5 6 7 8 9 10 0 0 0 0 0 0 0 0 0 0 0 0 1 2 6 0 0 6 6 6 6 6 6 6 6 6 2 2 3 0 0 6 6 9 9 9 9 9 9 9 3 6 5 0 0 6 6 9 9 9 9 11 11 14 4 5 4 0 0 6 6 9 9 9 10 11 13 14 5 4 6 0 0 6 6 9 9 12 12 15 15 15 #include<bits/stdc++.h> using namespace std; const int N=256; int dp[N][N]; int V;//容量 int c[N],w[N];//耗费 价值 int main(){ cin>>V; int n; cin>>n; for(int i=1;i<=n;i++){ cin>>c[i]>>w[i]; } for(int i=1;i<=n;i++){ for(int j=1;j<=V;j++){ dp[i][j]=dp[i-1][j]; if(j>=c[i]){ //如果空间大于当前背包耗费 dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]); } } } cout<<dp[n][V]<<endl; return 0; }
空间优化:
1. dp数组每次使用了上一层的数据,所以可以将二维数组的行数缩短到2,这样取模就行。
#include<bits/stdc++.h> using namespace std; const int N=256; int dp[2][N]; int W;//容量 int v[N],w[N];//价值 体积 int main(){ cin>>W; int n; cin>>n; for(int i=1;i<=n;i++){ cin>>v[i]>>w[i]; } //dp[i][j] //i表示当前处理第几个背包 j表示背包容量 for(int i=1;i<=W;i++){ //初始化第零行 前1件物品放到一个容量为i(1~W)的背包中可以获得的最大价值 dp[1][i]=(i>=w[1]?v[1]:0); } for(int i=2;i<=n;i++){ for(int j=1;j<=W;j++){ dp[i%2][j]=dp[(i-1)%2][j]; if(j>=w[i]){ //如果空间大于 dp[i%2][j]=max(dp[(i-1)%2][j],dp[(i-1)%2][j-w[i]]+v[i]); } } } cout<<dp[n%2][W]<<endl; return 0; }
2.空间占用还能缩减到O(V)的一维数组,我们想要的效果就是,第i次循环后,dp[v]中存储的是前i个物体放到容量v时的最大价值,也就是说每次只保存之前二维数组中一行的数据。
那么问题就是如何像二维数组那样取到之前状态的值呢?也就是说在求dp[i][j]的时候怎么获得dp[i-1][j]和dp[i-1][j-Ci]的值呢?
可以这样想,在准备进行第i次循环时,dp[v]中存储的是第 i - 1 次循环时存下的值,也就是我们这次训循环需要的值。那么这次循环的时候为了在不覆盖掉上次的值之前取到需要的值,我们要从后面进行遍历;假设背包容量为10,当前物品质量为3,我们先考虑在背包容量为10的情况下,将前i个物品放入背包的最大价值。然后这时候需要需要dp[10]和dp[10-3]的值,注意这时的dp[10]和dp[10-3]是上次循环留下的值。
//伪代码 for i=1..N //枚举物品 for v=V..0 //枚举容量,从大到小 f[v]=max{f[v],f[v-weight[i]] + cost[i]};
#include<bits/stdc++.h> using namespace std; int dp[1010]; int c[21],w[21];//耗费 价值 int main(){ int N,V; cin>>N>>V; for(int i=1;i<=N;i++){ cin>>w[i]>>c[i]; } 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]); } } cout<<dp[V]<<endl; return 0; }
多重背包
每个物品的数量不在是1,而是Ni。
实际上零一背包是特殊的多重背包
#include<bits/stdc++.h> using namespace std; const int N=256; int dp[N][N]; int c[N],w[N];//空间耗费 物品价值 int a[N];//物品数量 int main(){ int n,V; cin>>n>>V; for(int i=1;i<=n;i++){ cin>>a[i]>>c[i]>>w[i]; } for(int i=1;i<=n;i++){ for(int j=1;j<=V;j++){ for(int k=0;k<=n[i];k++){ if(j>=c[i]*k){ dp[i][j]=max(dp[i][j],dp[i-1][j-k*c[i]]+w[i]*k) } //当k==0时 //dp[i][j]=max(dp[i][j],dp[i-1][j]) // =dp[i-1][j] //当k==1时 //dp[i][j]=max(dp[i][j],dp[i-1][j-c[i]]+w[i]) } } } cout<<dp[n][V]; return 0; }
完全背包
每个物品可选数量无限
#include<bits/stdc++.h> using namespace std; const int N=256; int dp[N][N]; int c[N],w[N];//空间耗费 物品价值 int a[N];//物品数量 int main(){ int n,V; cin>>n>>V; for(int i=1;i<=n;i++){ cin>>a[i]>>c[i]>>w[i]; } for(int i=1;i<=n;i++){ for(int j=1;j<=V;j++){ for(int k=0;k*c[i]<=j;k++){ dp[i][j]=max(dp[i][j],dp[i-1][j-k*c[i]]+w[i]*k); } } } cout<<dp[n][V]; return 0; }
时间优化:
#include<bits/stdc++.h> using namespace std; const int N=256; int dp[N][N]; int c[N],w[N];//空间耗费 物品价值 int a[N];//物品数量 int main(){ int n,V; cin>>n>>V; for(int i=1;i<=n;i++){ cin>>a[i]>>c[i]>>w[i]; } for(int i=1;i<=n;i++){ for(int j=1;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]; } } } cout<<dp[n][V]; return 0; }
多重背包二进制优化