赞
踩
对于背包问题用文字很难描述清楚也很难理解,所以本篇仅描述基本思想框架,仅供学习参考!!!
三种背包:
问题描述:有 n件物品和一个容量是 m 的背包。每件物品只能使用一次。第 i件物品的体积是 w[i],价值是 c[i]。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
基本实现:
背包问题思想的核心在于‘“选择第i个物品/不选择第i个物品”
常设以下变量:
i:第i个物品
j:当前背包容量为j
m:表示背包容量
n:表示物品数量
w[i]:表示第i个物品的重量
c[i]:表示第i个物品的价值
dp[i][j]:表示前i个物品,背包容量为j时,得到的最大价值
情况一(不拿第i个物品,背包当前容量为j时):
这种情况第i
个物品不拿,那么就只能在第i-1
个物品中拿,而此时背包的容量没有变,依然是j
方程:dp[i][j]=d[i-1][j]
情况二 (拿第i个物品,背包当前容量为j时):
这情况当拿了第i
个物品之后,背包剩余容量为j-w[i]
,所以拿了第i
个物品之后要在剩余的i-1
个物品中将剩余的j-w[i]
容量装满
方程:dp[i][j]=d[i-1][j-w[i]]+c[i]
背包的思想是:在每次选择中只需要考虑拿与不拿,比较这两种情况哪一种收益最大,也就是情况一和情况二谁的dp[i][j]
的值最大
状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i])
代码演示:
#include<iostream> #include<algorithm> using namespace std; int n,m,w[5005],c[5005],dp[5005][5005]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){//w[0]和c[0]都初始化为0 cin>>w[i]>>c[i]; } //背包 for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ //不拿第i个物品 dp[i][j]=dp[i-1][j]; if(w[i]<=j){//背包容量大于第i个物品容量才有拿的可能 dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+c[i]);//选择两种情况的最大价值 } } } cout<<dp[n][m]<<endl;//输出物品数目为n,背包容量为m的最大价值 return 0; }
观察上面代码过程可以发现,我们处理dp[i][j]时只与i和i-1有关,dp[i][j]只与当前行和前一行有关,所以其实可以优化为滚动数组来实现,也就是将二维数组压缩成一维,减少了空间复杂度
注意:在01背包中,滚动数组需要逆向处理
状态转移方程:dp[j]=max(dp[j],dp[j-w[i]]+c[i])
(j表示哦当前背包大小)
逆向处理:
逆向处理滚动数组的精髓在于,如果没有动过这个位置的数,这个位置的数就会保留dp[i-1][j]
(二维数组的上一行)的旧值,若动过才会更新覆盖新值dp[i][j]
(二维数组的当前行),所以在进行比较的时候,保证了被比较的旧值不会发生改变,更新时,更新后的新值也不会再发生改变,只比较更新一次,就是01背包;
实现了同时对dp[i-1][j]
和dp[i][j]
的值可以用一维数组进行比较覆盖更新
示例:
我们假设此时i=5;w[5]=2,c[5]=2,j=6
对于dp[j]=max(dp[j],dp[j-w[i]]+c[i])
也就是dp[6]=max(dp[6],dp[6-2]+2)
括号内的dp[6]
就是dp[4][6]
继承下来的值(也就是不选第i个物品的情况)
括号内的dp[6-2]+2=dp[4]+2,dp[4]
就是dp[4][4]
继承下来的值(也就是选的情况)
发现dp[6]=5,dp[6-2]+2=6
,后者(选第i个物品的情况)值比较大,即dp[6]
更新为dp[6]=6
代码演示:
#include<iostream> #include<algorithm> using namespace std; int n,m,w[5005],c[5005],dp[5005]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>w[i]>>c[i]; } for(int i=1;i<=n;i++){ for(int j=m;j>=w[i];j--){//当j<w[i]时没有选择的机会,一个小优化 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); } } cout<<dp[m]<<endl; return 0; }
在上面的代码中,w[i]和c[i]其实都只使用了一次,所以对于这两个数组完全可以省略,用变量来代替
演示代码:
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,w,c,dp[5005];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w>>c;
for(int j=m;j>=w;j--){//当j<w[i]时没有选择的机会,一个小优化
dp[j]=max(dp[j],dp[j-w]+c);
}
}
cout<<dp[m]<<endl;
return 0;
}
问题描述:有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。第 i 种物品的体积是 w[i],价值是 c[i]。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
朴素方法我们只需在01背包代码上加一个循环就可以实现,题目说每个物品可以无限可用,那只需要引入一个for(int k=0;k<=j/w[i];k++)循环,表示放入k个物品求dp[j]最大值,但是k不能大于j/w[i],要不然会超出背包容量,还有相应的重量价值都需要乘k
演示代码:
#include<iostream> #include<algorithm> using namespace std; int n,m,w[50005],c[50005],dp[50005]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>w[i]>>c[i]; } for(int i=1;i<=n;i++){ for(int j=m;j>=w[i];j--){ for(int k=0;k<=j/w[i];k++) dp[j]=max(dp[j],dp[j-w[i]*k]+c[i]*k); } } cout<<dp[m]<<endl; return 0; }
此代码时间复杂度过大,不宜使用!!!
同样是选择与不选择
不选择:dp[i][j]=dp[i-1][j]
选择:dp[i][j]=dp[i][j-w[i]]
状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i][]j-w[i]+c[i])
可以看出,完全背包在选择的情况下,之后并不是在i-1
个物品里拿物品,而是可以继续在i
个物品里面拿物品,用同一行的数计算更新,就实现了物品可以多次选择
那么如何理解这个多次呢?
假设当前有A,B二件物品的重量价值都为1,手动模拟第一行如下(注意可以重复选择):
处理到第二行,也就是物品B的选择与不选择讨论
当j=1时
dp[2][1]=max(dp[1][1],dp[2][1-1]+1)=max(1,1)
选择与不选择结果一样,假设没有选择
当j=2时
dp[2][2]=max(dp[1][2],dp[2][2-1]+1)=max(2,2)
选择与不选择结果一样假设选择了B
当j=3时
dp[2][3]=max(dp[2-1][3],dp[2][3-1]+1)=max(2,3)
可见此时选择B得到最大值为3
到这里会惊奇得发现,dp[2][3]的价值由{A,B,B}得来,出现了重复选择,这就是完全背包的奥秘,可以实现重复选择
代码示例:
#include<iostream> #include<algorithm> using namespace std; int n,m,w[50005],c[10005],dp[10005][10005]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>w[i]>>c[i]; } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ dp[i][j]=dp[i-1][j];//不拿 if(j>=w[i]){//判断是否可以拿 dp[i][j]=max(dp[i][j],dp[i][j-w[i]]+c[i]); } } } cout<<dp[n][m]; return 0; }
二维完全背包同样可以优化成一维数组,减少空间复杂度
优化方法:顺向处理一维数组即可
顺向处理可以实现重复选择
具体结合01背包的逆向处理类比即可
代码示例:
#include<iostream> #include<algorithm> using namespace std; int n,m,w[10005],c[10005],dp[10005]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>w[i]>>c[i]; } for(int i=1;i<=n;i++){ for(int j=w[i];j<=m;j++){//j<w[i]没有选择的机会 dp[j]=max(dp[j],dp[j-w[i]]+c[i]); } } cout<<dp[m]; return 0; }
问题描述:有 N 种物品和一个容量是 V 的背包。第 i 种物品最多有 s[i] 件,每件体积是 w[i],价值是 c[i]。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
:多重度背包就是物品可以取规定次数,我们直接可以加一次循环for(int k=0;k<=s[i]&&v[i]*k<=j;k++);
来实现取规定次数
代码示例:
#include<iostream> #include<algorithm> using namespace std; int n,m,w[10005],c[10005],dp[10005],s[10005]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>w[i]>>c[i]>>s[i]; } for(int i=1;i<=n;i++){ for(int j=m;j>=w[i];j--){//j<w[i]没有选择的机会 for(int k=0;k<=s[i]&&w[i]*k<=j;k++){//保证取k此次物品重量小于背包容量 dp[j]=max(dp[j],dp[j-w[i]*k]+c[i]*k);//价值重量都需要乘k } } } cout<<dp[m]; return 0; }
可以将此问题转化为一个01背包问题,将当前物品的s[i]件都装入背包,然和当成01背包问题处理即可
装包代码:
int k=N+1;
for(int i=1;i<=N;i++){
while(s[i]>1){//说明有多个物品
v[k]=v[i];
w[k]=w[i];
k++;
s[i]--;
}
}
完整代码示例:
#include<iostream> #include<algorithm> using namespace std; const int M=500005; int N,V,v[M],w[M],s[M],dp[M]; int main(){ cin>>N>>V; for(int i=1;i<=N;i++){ cin>>v[i]>>w[i]>>s[i]; } //装包(转化为01背包问题) int k=N+1; for(int i=1;i<=N;i++){ while(s[i]>1){//说明有多个物品 v[k]=v[i]; w[k]=w[i]; k++; s[i]--; } } for(int i=1;i<=k;i++){ for(int j=V;j>=v[i];j--){ dp[j]=max(dp[j],dp[j-v[i]]+w[i]); } } cout<<dp[V]<<endl; return 0;
上述两种方法时间复杂度都过高,一般采用如下方法解决多重度背包问题
二进制优化方法,就是将物品拆成2的n次方个放入背包中,这样可以组成任意个数(小小的数学规律):
例如:当前物品有7个,拆成1,2,4,3个放入背包中(注意2的n次方不得大于背包容量,如上2^2还剩3个,则3个单独放入背包中),然后当成01背包处理,会发现其实拿1个,2个,3个,4个,5个,6个,7个都可以由这些个数的选择与不选择数得出,这就大大减少了时间复杂度
代码示例:
#include<iostream> #include<algorithm> #include<vector> using namespace std; int n,m,dp[500005]; struct Good{ int w,c; }; int main(){ cin>>n>>m; vector<Good>goods; //拆包,装包 for(int i=1;i<=n;i++){ int w,c,s; cin>>w>>c>>s; for(int k=1;k<=s;k*=2){ s-=k; goods.push_back({w*k,c*k}); } if(s>0){//剩余件数放入背包中 goods.push_back({s*w,s*c}); } } /*for(auto good: goods){ for(int j=m;j>=good.v;j++){ dp[j]=max(dp[j],dp[j-good.v]+good.w); } }*/ //接下来直接当成01背包处理 for(int i=0;i<=goods.size();i++){//这里注意,vector数组的下标是从0开始的,背包中物品的种类不再是n个,而是vector数组的大小 for(int j=m;j>=goods[i].w;j--){ dp[j]=max(dp[j],dp[j-goods[i].w]+goods[i].c); } } cout<<dp[m]<<endl; return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。