我是蒟蒻!!!
前言
由于此篇为完全背包(C++完全背包模板-优快云博客),多重背包(C++多重背包模板-优快云博客)等的基础,本人在此由浅入深提供3+1种写法
01背包题解【模板】:
题目描述
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入 #1
4 5
1 2
2 4
3 4
4 5
输出 #1
8
问题解释
问题很简单,不过看完后,你也许会产生一个想法:贪心,求出每个物品单位体积内的价值di,并按照其排序,求出最大值,不过我们很容易举出一个反例
假如 N=3,V=100
v1=50,w1=50 d1=1
v2=50,w2=50 d2=1
v3=51,w2=99 d3=99/51
显然,d3>d1=d2 所以我们应该选择物品3,不过正解为,选择物品1,2,总价值50+50>99,所以我们不能选择贪心,综上我们选择01搜索来求解最大值
01搜索:
一个物品,我们可以选也可以不选,所以我们可以写出二叉搜索
Ac代码如下:
#include <bits/stdc++.h>
using namespace std;
int n,V,ans;
int w[1005],v[1005];
void dfs(int i,int value,int vl)//第i个数,目前的价值,剩余的体积
{
if (i==n)//选完了
{
ans=max(ans,value);
return ;
}
if (vl-v[i+1]>=0) dfs(i+1,value+w[i+1],vl-v[i+1]);//选,总价值加wi,剩余体积减vi
dfs(i+1,value,vl);//不选,价值体积都不变
}
int main ()
{
int i,j;
cin>>n>>V;
for (i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
if (V-v[1]>=0) dfs(1,w[1],V-v[1]);//选,同上
dfs(1,0,V);//不选,同上
cout<<ans;
return 0;
}
因为这个搜索只有两种选法,操作简单,但是层数多,容易爆栈,所以我们可以选择运用递推
01背包无优化:
我们用一个数组f[i][j]表示前i个物品用了小于等于j个单位空间所存储的最大价值
我们可以从答案的来源f[N][V]得知,f[N][V]由f[N-1][V](不选)和f[N-1][V-v[N]]+w[N](减去第N个物品的体积,再将累计的价值加上第N个物品的价值)中较大的那个值得出
所以,我们可以从第N个物品类推第N-1个物品:f[N-1][V]=max(f[N-2][V],f[N-2][V-v[N-1]]+w[N-1])
我们又可以从第N个物品类推V-1体积时的情况:f[N][V-1]=max(f[N-1][V-1-v[N]]+w[N])
发现规律了吗?f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]) (其中j-v[i]>=0)
Ac代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
int f[1005][1005];
int v[1005],w[1005];
int n,V;
int main ()
{
int i,j;
scanf("%d%d",&n,&V);
for (i=1;i<=n;i++) scanf("%d%d",&v[i],&w[i]);
for (i=1;i<=n;i++)
{
for (j=1;j<=V;j++)
{
if (j-v[i]>=0) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
else f[i][j]=f[i-1][j];
}
}
printf("%d",f[n][V]);
return 0;
}
f[1000][1000]太大了,有没有什么方法优化空间呢?
01背包一维优化
我们发现f[i][j]只和第i-1层和本层的数值有关,所以我们不必开1000*1000的数组,可以将i%2,进行两层之间的传递
核心代码如下:
for (i=1;i<=n;i++)
{
for (j=v[i];j<=v;j++)
{
f[i%2][j]=max(f[(i+1)%2][j],max(f[i%2][j-v[i]]+w[i],f[i%2][j-1]));
//不选 选 传递前i个数的最大值
}
}
我们又可以发现,假如只用一层的一维数组,如果不进行修改的话,已经是第i-1层的最优值了,但如果按照前面的方法,就会对已有的i-1层数值进行修改,如f[i][j-v[i]]可能已经被改为这层的最大值,不满足只用一次
那么,f[i][j-v[i]]一定是前面的值对后面的值有影响(j-v[i],j不断增大,v[i]在本层不变),所以我们采用倒序循环就能完美的避免这个问题!
Ac代码如下:
#include <bits/stdc++.h>
using namespace std;
int n,V,ans;
int v[1005],w[1005],f[1005];
int main ()
{
int i,j;
cin>>n>>V;
for (i=1;i<=n;i++) cin>>v[i]>>w[i];
for (i=1;i<=n;i++)
{
for (j=v[i];j<=m;j++)
{
f[j]=max(f[j],max(f[j-v[i]]+w[i],f[j-1]));
}
}
cout<<f[V];
return 0;
}
成功Ac,完结撒花!
测试参考题目:P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
(注意将输入输出,数组大小进行修改)
如有不解或优化,可在评论区留言