[AcWing] 2. 01背包问题(C++实现)0-1背包问题模板题
如果只看为什么要逆序,请直接下拉至3. 解法
后附的可能存在的问题
.
在3. 解法
后附的可能存在的问题
,我给出了一个实例,可以清楚地看到,为什么正序会影响上一层的结果,而为什么逆序不会影响上一层的结果。
1. 题目
2. 读题(需要重点注意的东西)
思路:
闫式dp分析法
用闫式dp分析法分析0-1背包问题
未进行优化
能不能进行优化?
①
由于 i 这一层只与 i - 1 这一层有关,则可以使用滚动数组来优化空间复杂度,仅保留上一层结果即可,这样就能将其优化为一维:
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j > v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
但是此时,我们相当于用的是 f [ i ] f [ j - v[ i ] ]的状态,而并非 f [ i - 1] f [ j - v[ i ] ]的状态,因为 f [ i ] 把 f [ i - 1]的状态更新了。
②
因此,我们要想使用上一层的 j - v[i],就需要把 j 逆序遍历。
for(int i = 0; i <= n; i++)
for(int j = m; j >= v[i]; j--)
f[j] = max(f[j], f[j-v[i]]+w[i]);
3. 解法
---------------------------------------------------解法1:优化前---------------------------------------------------
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main(){
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 = 0;j <= m;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]);
}
}
cout << f[n][m]<< endl;
return 0;
}
---------------------------------------------------解法2:优化后---------------------------------------------------
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N];
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i++) cin >> v[i] >> w[i];
for(int i = 0; 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;
}
可能存在的问题
① 优化前为什么 要判断 if(j >= v[i])
?
我们在此刻面临着选不选 i 的问题,当前的背包容量是 j ,如果把背包全部清空都放不下 i ,那么只能将 i 舍弃,即为不选 i ,则为f[i][j] = f[i-1][j];
如果能放下 i ,选择 i 时的最大价值 = i 的价值 + 在剩下 i-1 个物品中选体积小于j-v[i]的最大价值,即max(f[i][j],f[i-1][j-v[i]]+w[i])
事实上,代码也可以这样写,表示选 i 和不选 i 两种情况
for(int i = 1;i <= n;i++){
for(int j = 0;j <= m;j++){
if(j <= v[i]) f[i][j] = f[i-1][j]; // 如果背包清空了也放不下i,则不选i
else {
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]); // 如果背包能放下i,则在i-1个物品中,选体积小于j-v[i]的价值最大者
}
}
}
② 优化后,为什么要逆序?
如果不逆序,代码为:
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(j > v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
此时,我们提出一种可能的情况:
最外层 i 循环到i
时,v[i] = 1 , m = 5
,我们会将 j 循环 1 2 3 4 5 ,
当j = 2
时,我们需要判断 f [ 2 ] = max ( f [ 2 ], f [ 1 ] + w [ i ] ) ;
如果此时,我们将f [ 2 ] 更新了
,那么在下一个 j 循环,j = 3 时,
我们要判断 f [ 3 ] = max ( f [ 3 ], f [ 2 ]
+ w [ i ] ) ;
那此时,我们使用的就是刚刚被更新的f [ 2 ],即 f [ i ] [ 2 ],而非上一层的 f [ i-1 ] [ 2 ],因为上一层的 f [ i-1 ] [ 2 ]已经被更新成当前层的f [ i ] [ 2 ]了
注意:逆序后,我们的循环条件是j >= v[i]
,代码为:
for(int i = 1; i <= n; i++)
for(int j = m; j >= v[i]; j--)
if(j > v[i]) f[j] = max(f[j], f[j - v[i]] + w[i]);
又因为,循环条件是j >= v[i]
,所以 if 判断语句也可以删除,即:
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]);
此时,我们再来看刚刚那种情况,看更新当前层(第 i 层
)f [ j ] 是否会影响上一层(i - 1层
)的f [ j ]
最外层 i 循环到i
时, v[i] = 1 , m = 5
,我们会将 j 循环 5 4 3 2 1,
当j = 5
时,我们需要判断 f [ 5 ] = max ( f [ 5 ], f [ 4 ] + w [ i ] ) ;
看见了吗,我们此时只会更新 f [ 5 ] 的值,在下一次判断时
我们需要判断 f [ 4 ] = max ( f [ 4 ], f [ 3 ] + w [ i ] ) ;
发现了吗,刚刚更新的值f [ 5 ]
与下一次的判断f [ 4 ]
无关,因此,只需要将 j 逆序遍历
,就不会影响上一层的f[j - v[i]]
4. 可能有帮助的前置习题
5. 所用到的数据结构与算法思想
- 动态规划
- 0-1背包问题
6. 总结
0-1背包问题模板题,理解思想并背下代码。