1、01背包问题
这里还是要老生常谈一下,我们一定要明确每个状态定义,f [ j ]表示容量不超过 j 所能带走的最大价值。因此 f 初始化的值均为0。
朴素版本
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
//这里记得写在外面,因为不一定每次都会有j>=v[i]但一定有f[i][j]=f[i-1][j]
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
01背包价值从大到小遍历
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--){
f[j]=max(f[j],f[j-v]+w);
}
}
cout<<f[m]<<endl;
return 0;
}
2、完全背包问题
完全背包问题的状态定义与上题一致,f [ j ]表示容量不超过 j 所能带走的最大价值。因此 f 初始化的值均为0
朴素版本:
//由于三层循环复杂度过高,因此常常需要进行简化
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k*v[i]<=j;k++){
//这里k=0就代表了不选择j这个物品
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
完全背包第二维价值从小到大枚举
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
int v,w;
cin>>v>>w;
for(int j=v;j<=m;j++){
f[j]=max(f[j],f[j-v]+w);
}
}
cout<<f[m]<<endl;
return 0;
}
3、多重背包问题
本题中我们使用朴素做法进行求解,f [ i ][ j ]表示前 i 个物品中,容量不超过 j 所能带走的最大价值,注意当n,v,w范围变大时,朴素做法可能会超时。
之前的题可能没注意,我们每次 i 的下标从1开始,j 的下标从0开始,本题用于计数的 k 同样也是从0开始(0代表一个都不拿)
#include <iostream>
using namespace std;
const int N = 110;
int f[N][N];
int v[N],w[N],s[N];
int n,m;
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=1;j<=m;j++){
for(int k=0;k<=s[i];k++){
if(j>=k*v[i]) f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
cout<<f[n][m];
return 0;
}
4、分组背包问题
这里我们介绍朴素和非朴素两种写法,朴素写法中f [ i ][ j ]表示从前 i 组中选,体积不超过 j 所能带走的最大价值
记得要记录每组元素个数
朴素版本:
#include <iostream>
using namespace std;
const int N = 110;
int f[N][N],v[N][N],w[N][N];
int s[N];
int main(){
int n,m;
cin>>n>>m;
//i表示第i组
for(int i=1;i<=n;i++){
cin>>s[i];
for(int j=1;j<=s[i];j++){
cin>>v[i][j]>>w[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
//k=0表示第i组我一个不要
for(int k=0;k<=s[i];k++){
if(j>=v[i][k]) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
类似01背包问题节省掉一维空间
体积是从大到小枚举,与01背包类似
#include <iostream>
using namespace std;
const int N = 110;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
for(int j=1;j<=s[i];j++){
cin>>v[i][j]>>w[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=m;j>=1;j--){
for(int k=1;k<=s[i];k++){
if(j>=v[i][k]) f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
}
cout<<f[m];
return 0;
}
5、二维背包问题
与一维背包相比,多了一个限制条件,即状态表示多了一维,f[ i ][ j ][ k ]:表示前i个物品,体积不超过j,重量不超过k所能获得的最大价值
朴素写法:
//三维循环,类似01背包
for(int i=1;i<=n;i++){
for(int j=0;j<=m1;j++){
for(int k=0;k<=m2;k++){
f[i][j][k]=f[i-1][j][k];
if(j>=v[i] && k>=m[i]) f[i][j][k]=max(f[i][j][k],f[i-1][j-v[i]][k-m[i]]+w[i]);
}
}
}
我们可以发现整道题思路都与01背包类似,因此优化方法也相似
同样省略表示物品顺序的第一维,另外两个维度的体积和重量都是从大到小开始枚举
#include <iostream>
using namespace std;
const int N = 110;
int f[N][N];
int n,m1,m2;
int main(){
cin>>n>>m1>>m2;
for(int i=0;i<n;i++){
int v,m,w;
cin>>v>>m>>w;
for(int j=m1;j>=v;j--){
for(int k=m2;k>=m;k--){
f[j][k]=max(f[j][k],f[j-v][k-m]+w);
}
}
}
cout<<f[m1][m2];
return 0;
}