背包问题(1)
0/1背包
问题描述:
有n件物品,每件物品的重量为w[i],价值为v[i]。现有一个容量为C的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有一件。
问题分析:
用dp[i][j]表示有前i件物品、背包容量为j时背包所能放下物品的最大价值。
对每件物品进行决策,不选(0)或是选(1)。
状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]
代码实现:
#include<iostream> using namespace std; #define MAX_CAPATICY 100 #define MAX_QUANTITY 100 int w[MAX_QUANTITY+2];///物品重量 int v[MAX_QUANTITY+2];///物品价值 int dp[MAX_QUANTITY+2][MAX_CAPATICY+2]; int c,n;///背包容量和物品数量 void print() { int i=n,j=c; while(i>=1){ if(dp[i][j]==dp[i-1][j]){///第i个物品没有被选择 i--; } else{ cout<<i<<' '; j-=w[i]; i--; } } } int main() { while(cin>>c>>n){ for(int i=1;i<=n;++i){ cin>>w[i]>>v[i]; } for(int i=1;i<=n;++i){///当前物品数量 for(int j=1;j<=c;++j){///当前背包容量 if(j>=w[i]){ dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); } } } cout<<dp[n][c]<<endl; //print();///输出被选择的物品 } return 0; }
优化:
根据状态转移方程可以看出dp[i][j]只与dp[i-1]行(上一行)有关
所以可以将二维数组优化成 dp[MAX_QUANTITY+2][2] 只有两行的滚动数组
如果改变内层循环的顺序,从后往前推,j较大时dp[j]发生改变不会影响到j较小时dp[j]的计算,则可以用一维数组来实现
优化后的代码实现:
#include<iostream> using namespace std; #define MAX_CAPATICY 100 #define MAX_QUANTITY 100 int w[MAX_QUANTITY+2];///物品重量 int v[MAX_QUANTITY+2];///物品价值 int dp[MAX_QUANTITY+2];///使用一维数组 int c,n;///背包容量和物品数量 int main() { while(cin>>c>>n){ for(int i=1;i<=n;++i){ cin>>w[i]>>v[i]; } for(int i=1;i<=n;++i){///当前物品数量 for(int j=c;j>=w[i];--j){///当前背包容量 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } cout<<dp[c]<<endl; } return 0; }
完全背包
问题描述:
有n种物品,每件物品的重量为w[i],价值为v[i]。现有一个容量为C的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都有无限件。
问题分析:
完全背包与0/1背包的唯一不同之处就在于完全背包问题中每种物品有无限件。
同样用dp[i][j]表示有前i件物品、背包容量为j时背包所能放下物品的最大价值。
对每种物品进行决策,不选(0)或是选(至少一件)
状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i][j-k*w[i]]+k*v[i])
代码实现:
#include<iostream> using namespace std; #define MAX_CAPATICY 100 #define MAX_QUANTITY 100 int w[MAX_QUANTITY+2];///物品重量 int v[MAX_QUANTITY+2];///物品价值 int dp[MAX_QUANTITY+2][MAX_CAPATICY+2]; int c,n;///背包容量和物品种数 int main() { while(cin>>c>>n){ for(int i=1;i<=n;++i){ cin>>w[i]>>v[i]; } for(int i=1;i<=n;++i){ for(int j=1;j<=c;++j){ for(int k=0;k<=c/w[i];++k){ if(j>=k*w[i]){ dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]); } } } } cout<<dp[n][c]<<endl; } return 0; }
优化:
同样也可以用一维数组来实现,不但大大优化了空间复杂度,也优化了时间复杂度
完全背包的一维数组实现循环是顺序的,与0/1背包不同,能取无限件同类物品的完全背包正好需要在前面选择过的基础上进行选择
优化后的代码实现:
#include<iostream> using namespace std; #define MAX_CAPATICY 100 #define MAX_QUANTITY 100 int w[MAX_QUANTITY+2];///物品重量 int v[MAX_QUANTITY+2];///物品价值 int dp[MAX_QUANTITY+2];///使用一维数组 int c,n;///背包容量和物品数量 int main() { while(cin>>c>>n){ for(int i=1;i<=n;++i){ cin>>w[i]>>v[i]; } for(int i=1;i<=n;++i){///当前物品数量 for(int j=w[i];j<=c;++j){///当前背包容量 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } cout<<dp[c]<<endl; } return 0; }
多重背包
问题描述:
有n种物品,每件物品的重量为w[i],价值为v[i]。现有一个容量为C的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都有m[i]件。
问题分析:
多重背包与完全背包的思路差不多
用dp[i][j]表示有前i件物品、背包容量为j时背包所能放下物品的最大价值。
对每种物品进行决策,不选(0)或是选(至少一件)
状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i][j-k*w[i]]+k*v[i])
仅仅是这里k多了一个限制条件 k<=m[i]
代码实现:
#include<iostream> using namespace std; #define MAX_CAPATICY 100 #define MAX_QUANTITY 100 int w[MAX_QUANTITY+2];///物品重量 int v[MAX_QUANTITY+2];///物品价值 int m[MAX_QUANTITY+2];///第i种物品的件数 int dp[MAX_QUANTITY+2][MAX_CAPATICY+2]; int c,n;///背包容量和物品种数 int main() { while(cin>>c>>n){ for(int i=1;i<=n;++i){ cin>>w[i]>>v[i]>>m[i]; } for(int i=1;i<=n;++i){ for(int j=1;j<=c;++j){ for(int k=0;k<=c/w[i]&&k<=m[i];++k){///比完全背包多一个限制条件 if(j>=k*w[i]){ dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]); } } } } cout<<dp[n][c]<<endl; } return 0; }
优化:
同样可优化为一维数组的写法
当某种物品总重量超过背包容量 m[i]*w[i]>c 时,可以当成完全背包来计算
否则,转换成0/1背包求解
为进一步优化时间复杂度 这里还可以用2进制想法
把一种物品分成若干份,将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),m[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。这样1-m[i]的所有数都可以被表示出来
优化后的代码实现:
#include<iostream> using namespace std; #define MAX_CAPATICY 100 #define MAX_QUANTITY 100 int w[MAX_QUANTITY+2];///物品重量 int v[MAX_QUANTITY+2];///物品价值 int m[MAX_QUANTITY+2];///第i种物品的件数 int dp[MAX_QUANTITY+2];///使用一维数组 int c,n;///背包容量和物品数量 int main() { while(cin>>c>>n){ for(int i=1;i<=n;++i){ cin>>w[i]>>v[i]>>m[i]; } for(int i=1;i<=n;++i){ if(m[i]*w[i]>=c){///该物品总重量超过背包容量,当成完全背包 for(int j=w[i];j<=c;++j){ dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } else{ for(int j=c;j>=w[i];--j){ int k=1,num=m[i]; while(k<=num){ dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]); num-=k; k<<=1; } dp[j]=max(dp[j],dp[j-num*w[i]]+num*v[i]); } } } cout<<dp[c]<<endl; } return 0; }