目录
1、01背包
题面:
01背包是什么呢,每件物品只有1件,但是体积只有V,怎样才能让装的物品最大价值呢?
这里我们先把数组的意思讲明白,dp[i][j]就是考虑前i件物品,体积限制为j的情况下的最大价值
我们可以把一件物品的选择方案分为包含当前物品的和不包含当前物品的,并且给一个体积j,如果不包含当前物品,那么就是只考虑前i-1件物品,体积限制为j的情况;而包含当前物品的情况就是考虑前i-1件物品,体积限制为j-v[i]的情况加上一个当前物品的价值w[i];
那么我们的状态转移方程就出来了:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
上代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int v[MAXN]; // 体积
int w[MAXN]; // 价值
int f[MAXN][MAXN]; // f[i][j], j体积下前i个物品的最大价值
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n, m;
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;
}
好的,二维我们了解了,我们再看一下这个状态转移方程,发现i只和i-1有关,那么我们是不是可以直接把i删掉,因为反正下一行的是根据上一行更新的,这不就滚动数组嘛。但其实不是的,还要将j从后往前遍历,这是为什么呢?
因为再循环j的时候,当前的j可能已经不代表第i-1行了,因为f[j-v[i]]会更新f[j],我们只有从后往前遍历,让他不被污染,才能保证他用的不是i行的,而是i-1行的
01背包:更新需要用到i-1行,j要从后往前遍历
上代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e3+10;
int n,m;
int v[N],w[N];
int dp[N];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++) cin>>v[i]>>w[i];
for(int i=0;i<n;i++){
for(int j=m;j>=v[i];j--){
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[m];
return 0;
}
2、完全背包
题面
大致就是01背包不是每个只能拿1个吗,那完全背包就是每个都可以拿无数个,取总价值最大的情况,怎么做呢?
我们可以把每件物品设为k的最大值,为什么有最大值呢,因为总体积要小于v嘛,我们只要让k*v[i]<V就好了,然后就遍历一遍,跟前面做法类似
f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w......]
f[i][j-v]=max( f[i-1][j-v]+w,f[i-1][j-2v]+2w.......]
然后我们就发现这两段好像是不是有什么相似的地方???
于是我们得出了状态转移方程 f[i][j]=max(f[i][j],f[i][j-v]+w)
好这里把k次循环删掉了,那么我们发现i好像不用管???于是就变成了一维
完全背包问题:不与前一层有关,从前往后循环
上代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e3+10;
int v[N],w[N];
int n,m;
int dp[N];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++) cin>>v[i]>>w[i];
for(int i=0;i<n;i++)
for(int j=v[i];j<=m;j++)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
cout<<dp[m];
return 0;
}
3、多重背包问题
题面
简单意思就是它不像01背包只能取一个,也不想完全背包每个可以取无数次,而是每个都可以取给定数值次
那很简单了,这不就01背包的改进吗,我多存几个进去不就是了吗,确实是这样的,但是这里有个用二进制优化版本:
一个数可以将其转化成2进制的表示方式,用二进制可以组成任意的数,那我们可以将当前的数转化成2进制表示,用剩下的数单独组成一组,将这些选项加到01背包中去就阔以啦
(2022年蓝桥杯javab组国赛考了一道完全背包的题目,但是这道题有陷阱哦,看到的朋友可以做一做)
上代码
注意,数组大小要开到N*logN,即物品个数*log物品最大数量,否则可能各种错误
多重背包我们也是用01背包去做的,所以也是从后往前遍历
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 15010, M = 2010;
int n, m;
int v[N], w[N];
int dp[M];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
int cnt=0;
for (int i = 1; i <= n; i ++ )
{
int a, b, s;
cin >> a >> b >> s;
int k = 1;
while (k <= s)
{
cnt ++ ;
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
if (s > 0)
{
cnt ++ ;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n=cnt;
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout << dp[m] << endl;
return 0;
}
4、分组背包
分组背包的意思是给定N个组,每组能选1个物品,取价值最大,其实也是一样的吧,用01背包来做就好了
再多循环一次数组长度,看看当前这一组的每种体积更新一下,最终得出答案
分组背包问题:用01背包做,从后往前遍历
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int n,m;
int v[N][N],w[N][N],s[N];
int dp[N];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
for (int j = 0; j < s[i]; j ++ )
cin >> v[i][j] >> w[i][j];
}
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 0; j -- )
for (int k = 0; k < s[i]; k ++ )
if (v[i][k] <= j)
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
cout<<dp[m]<<endl;
return 0;
}
总结:
1、当前第i个物品的更新要用到i-1层的,要从后往前遍历--01背包
2、当前第i个物品的更新只跟i层有关的,从前往后遍历--多重背包