闫神视频笔记
视频链接
01背包问题是最基础的背包问题,特点是:每种物品仅有一件,可以选或者不选
题目链接(acwing)
解题思路:
每个物品有选和不选两种选择。我们可以用一个二维数组f[i][j]代表前i个物品,体积为j的最大价值。在体积为j的情况下,如果选择第i个物品,则前i个物品的最大价值为前i-1个物品,在体积为j-第i个物品体积的情况下的最大价值加上第i个物品的价值;如果不选择第i个物品,则前i个物品的最大价值为前i-1个物品,在体积为j的情况下的最大价值。而两种选择中的最大值,即为前i个物品在体积为j的情况下的最大价值。最后输出f[n][m](即前n个物品,体积为m的最大价值)即可。
关于“直接输出f[n][m]的解释:”
f[n][m] 的准确意思是前n个物品中,体积小于等于m的最大价值,f[n][m]为最终答案的前提条件是数组的初始状态都为0。如果只是f[0][0]为0,其他均为-INF,那么在计算过程中,可能会出现错误。比如:在计算f[a][b]时,需要f[a-1][b-v[a]]的数据(其中v[a]代表第a个物品的体积),如果不存在前a-1个物品,体积恰为b-v[a]的情况,那么计算出来的结果就变成-INF+w[a](其中w[a]代表第a个物品的价值),这样肯定是错误的。以这种方式运算的话,正确的结果就在f[n][j]中,理由就是在最大价值的情况下,体积如果为x,则其最大价值就保存在f[n][x]中,因为我们不知道最大价值的体积是多少,所以我们需要遍历f[n][j],找出其中的最大值,即是最大价值,而f[n][m]保存的则是前n个物品中,体积恰为m的最大价值(但是题目并不要求体积恰为m)。而为什么将数组的初始状态都设为0,最终结果就是f[n][m]? 原因就是状态转移的定义从体积恰为m变成了体积小于等于m,这样f[n][m]保存的则是前n个物品中,体积小于等于 m的最大价值,故其一定正确。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int f[N][N];//前i个物品,总体积为j的最大价值
int v[N],w[N];//保存物品的体积与价值
int main(){
// freopen("1.txt","r",stdin);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
for(int j=1;j<=m;j++){
if(v[i]<=j){
f[i][j]=max(f[i-1][j-v[i]]+w[i],f[i-1][j]);
}else{
f[i][j]=f[i-1][j];
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
优化空间复杂度:O(N*V)->O(V)
原理:
我们可以先观察一下二维数组f中的数据,如下图所示,
可以知道程序将从左到右,从上到下依次遍历各个数据。每一行代表一轮循环(指代码中的第二层循环)。
我们用红色代表当前正在计算的数据,而绿色代表此轮循环(指第二层的循环)刚计算出来的值,蓝色代表此轮循环需要用到的数据(即上一轮循环计算出来的数据)。而在蓝色之前的数据是不需要的,或者说是过时的,已经被利用过了。所以截至目前有用的数据仅仅只是蓝色和绿色的数据。
现在我们换一个遍历的方式,将第二层循环改成从右到左,则上图将变成下图所示,
可以发现,需要保存的数据(或者说截至目前仍有用的数据)明显少了。
为什么原本需要保存的蓝色数据有些不需要保存了呢?
通过代码
if(v[i]<=j){
f[i][j]=max(f[i-1][j-v[i]]+w[i],f[i-1][j]);
}else{
f[i][j]=f[i-1][j];
}
可以知道,在计算红色数据时,只会用到列号比红色数据的列号小或相等的蓝色数据,再加上第二层循环是从右向左遍历的,因此,计算完红色数据后,列号比红色数据的列号大或相等的蓝色数据都不再需要了。更准确地说,在计算红色数据前,需要保存的数据是蓝色和绿色的数据;计算完红色的数据后,除了红色上面的蓝色数据,其余有色数据都是需要保存的。可以发现同一时刻需要保存的数据正好具有不同的列号,并且正好与一行数据的个数相同,所以我们可以直接用一个一维数组来保存这些数据。如下图所示
其中,最右边的蓝色数据代表原本的红色数据。利用蓝色数据,计算出红色的数据,红色的数据就变成了绿色的数据。
改进之后的代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 1010;
int f[N];//体积为i的最大价值
int v[N],w[N];//保存物品的体积与价值
int main(){
// freopen("1.txt","r",stdin);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[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;
}