一、0-1背包问题
问题描述

关键:每件物品只能使用一次。
分析
「0-1 背包」不断对第 i 个物品的做出决策,「0-1」正好代表不选与选两种决定。
(1)二维实现
f[i][j] :前i个物品,背包容量为j下的最大价值。
N件物品,N次决策,每一次对第i件物品的决策,由之前的状态更新。
如何更新f[i][j]?
1)当前背包容量不够,无法选择当前物品 j<v[i] f[i][j]=f[i-1][j]
2)当前背包容量够,可以选择是否选当前物品 f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1e3+10;
int n,m,v[N],w[N],f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(j<v[i]){
f[i][j]=f[i-1][j];
}
else{
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
(2)一维实现
由于f[i][j]实际上只依赖于f[i-1][j](也就是说只要i从1到n就可以模拟出这种滚动的状态),可以使用一维数组实现。
注意:内层循环必须逆向遍历,否则会重复计算(变成完全背包问题)。
原因就在于:f[j]=max(f[j],f[j-v[i]]+w[i]),只会用较小下标的f值更新较大下标的f值。如果正向遍历,如果f[j-v[i]]已经用了物品i,而f[j]无法区分是否物品i已经被使用了,就会多次使用物品i,变成了完全背包问题。如果逆向更新,此时的f[j-v[i]]还没有更新,绝对没有使用物品i,就不会导致物品i被使用多次。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1e3+10;
int n,m,v[N],w[N],f[N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
二、完全背包问题
问题描述

关键:每件物品都有无限件可以使用。
分析
完全背包与0-1背包不同的点在于,物品是无限多的。
我们已经在0-1背包问题中讨论过,内层循环需要从大到小,这样保证每个物品最多用一次,而在完全背包中,则需要从小到大,这样每个物品就可以用多次了。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1e3+10;
int n,m,v[N],w[N],f[N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
三、多重背包问题
问题描述
关键:物品既不是1件也不是无限多,而是有限件,需要用一个数组s[N]来保存每种物品的件数。

分析
(1)朴素版本
这个时候,我们需要加一重循环来判断一种物品到底拿多少件,第二层循环还是要倒序保证物品不会多拿。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=1e2+10;
int n,m,v[N],w[N],f[N],s[N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i]>>s[i];
}
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
for(int k=0;k*v[i]<=j&&k<=s[i];k++){
f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
}
}
}
cout<<f[m]<<endl;
return 0;
}
(2)改进版本
上面的代码时间复杂度是O(n^3),对于n比较大的题是过不了的,那么怎样才能够改进呢?
那就是对物品进行拆分,如果暴力拆,将物品拆为0-1背包的形式,就可以降到O(n^2)。但是如果物品数量特别多,将导致n暴增。
有没有更好的解决方案呢?
那就是:任何整数c可以表示为若干 2的幂次之和,通过组合这些幂次的数,可以覆盖0到c的所有可能取值。
-
假设c=13,可拆分为 1,2,4,6(因为 1+2+4+6=13)。
-
通过选/不选这些拆分后的数,可以组合出 0∼13之间的任意数量(如 5=1+4)。
剩下的问题就套用0-1背包的模板解决即可。

n<=1000,s<=2000 N需要开到n*log2s(向上取整)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=2e4+10;
int n,m,v[N],w[N],f[N],cnt;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
int a,b,c;
cin>>a>>b>>c;
int t=1;
while(t<=c){
v[++cnt]=t*a;
w[cnt]=t*b;
c-=t;
t*=2;
}
if(c){
v[++cnt]=c*a;
w[cnt]=c*b;
}
}
n=cnt;
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
到这里,背包问题彻底结束,恭喜🎉。
&spm=1001.2101.3001.5002&articleId=150350143&d=1&t=3&u=5ef546e2e0314859afae447007632ac6)
1万+

被折叠的 条评论
为什么被折叠?



