一、01背包问题
问题描述:给定n件物品,容量为m的背包,在每件物品最多只能装一个的情况下,求背包所能装下的最大价值。
主要思想:用数组v[ i ]存储物品 i 的价值,数组w[ i ] 存储物品 i 的重量,数组 f[ i ][ j ] 表示在只考虑前 i 件物品,背包容量为 j 的情况下,背包所能装下的最大价值。f[ i ][ j ]在 f[ i-1 ][ j ]的基础上考虑,有装和不装两种情况:要么第 i 件物品不装进背包,f[ i ][ j ] = f[ i-1 ][ j ];要么第 i 件物品装进背包,f[ i ][ j ] = max(f[ i ][ j ],f[ i-1 ][ j - v[ i ]] + w[ i ])。通过这种递归关系可以求出范围内各种组合的最大价值,f[ n ][ m ]即为最终答案。
代码:
①二维表示
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N],f[N][N];
int main(){
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++){
f[i][j]=f[i-1][j];
if(j>=v[i]) //第i件物品可以装进再执行
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//取装和不装两种结果的最大价值
}
}
cout<<f[n][m]<<endl;
return 0;
}
②一维表示
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N];
int F[N];
int main(){
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=m;j>=v[i];j--)//详见注释
F[j]=max(F[j],F[j-v[i]]+w[i]);
}
cout<<F[m]<<endl;
return 0;
}
注:由于一维数组用到了滚动数组的概念(即新一层的内容只和上一层有关,可以只定义两层,每次进行滚动覆盖),若是正序遍历从左往右更新,左边的F已经被更新成第 i 层,而递归公式中用到的是第 i-1层的值。逆序遍历从右往左更新,能够保证左边的F还没有被更新,仍然是第 i-1 层的值。 j < v[ i ]时不需要做改变,可以不用遍历,循环条件只到 j >= v[ i ]。
二、完全背包问题
问题描述:给定n件物品,容量为m的背包,在每件物品能装无数个的情况下,求背包所能装下的最大价值。
主要想思:f[ i ][ j ]在 f[ i-1 ][ j ]的基础上考虑,第 i 个物品可以装0个、1个、2个……k个,取k种装法中价值最大的一种,即 f[ i ][ j ] = max( f[ i ][ j ] , f[ i-1 ][ j - v[ i ] * k ] + w[ i ] * k ),朴素做法为增加一层循环,遍历k能取的范围,但三重循环计算时间太大。由f[ i ][ j ] = f[ i ][ j-v[ i ] ] + w[ i ]的递归关系,可以得出f[ i ][ j ] = max( f[ i ][ j ] , f[ i ][ j - v[ i ] ] + w[ i ] )。
注意完全背包递推公式与01背包递推公式的区别,完全背包递推公式是在第 i 层里找,01背包递推公式是在第 i-1 层里找。
代码:
①二维表示
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N],f[N][N];
int main(){
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++){
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]); //注意是在第 i 层找
}
}
cout<<f[n][m]<<endl;
return 0;
}
②一维表示
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N];
int F[N];
int main(){
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=v[i];j<=m;j++){//注意是正序遍历
F[j]=max(F[j],F[j-v[i]]+w[i]);
}
}
cout<<F[m]<<endl;
return 0;
}
注:由于完全背包递推公式是在第 i 层里找最大,只需要正序遍历即可,建议与01背包一维表示的逆序遍历对比理解。
三、多重背包问题
问题描述:给定n件物品,容量为m的背包,在每件物品最多装s[ i ]个的情况下,求背包所能装下的最大价值。
主要思想:多重背包问题相当于限制了k最大为s[ i ]的完全背包问题,可以在朴素完全背包问题解法的基础上增加限制求解,但三重循环容易超时。而由于限制了k的最大值,多重背包问题中f[ i ][ j ]与f[ i ][ j-v[ i ] ]的项数不匹配,不能直接套用完全背包问题的优化方法。
可以采用二进制优化,把s[ i ]拆分成由1、2、4……、c(c < ,为剩余的数)个物品组成的logs[ i ]组,每组物品抽象成01背包问题,只能不拿或者全拿,不同拿法可以涵盖0到s[ i ]的所有个数。经过这样优化,可以从遍历s[ i ]次减少到遍历logs[ i ]次,能够把时间复杂度从NVS降低到NVlogS。
代码:
①限制k的朴素解法
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N],s[N],f[N][N];
int main(){
int n,m;
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*v[i]<=j;k++)//限制k最大为s[ i ]
f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
}
cout<<f[n][m]<<endl;
return 0;
}
②二进制优化的一维表示
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N],w[N],s[N];
int F[N];
int main(){
int n,m;
cin>>n>>m;
int cnt=0;//cnt最大为n*logs
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;//k为组内物品个数
}
if(s>0){//存由c个物品组成的最后一组
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
n=cnt;//一共有cnt组,相当于cnt个物品的01背包问题
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]);//01背包问题的一维解法
}
}
cout<<F[m]<<endl;
return 0;
}
四、分组背包问题
问题描述:给定n组物品,容量为m的背包,在同一组物品最多只能选一个的情况下,求背包所能装下的最大价值。
主要思想:枚举第 i 组中选第几个物品时价值最大,f[ i ][ j ] = max( f[ i-1 ][ j ] , f[ i-1 ][ j-v[ i , k ] ] + w[ i , k ])。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int v[N][N],w[N][N],s[N];
int F[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];//存第i组的物品数量
for(int k=1;k<=s[i];k++)
cin>>v[i][k]>>w[i][k];
}
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){//在第i-1层找,逆序遍历
for(int k=1;k<=s[i];k++){//遍历第i组的物品
if(v[i][k]<=j)
F[j]=max(F[j],F[j-v[i][k]]+w[i][k]);
}
}
}
cout<<F[m]<<endl;
return 0;
}
参考资料:ACWing算法基础