背包问题是动态规划中一类相当经典的问题 ,下面 ,我将介绍我在使用豆包MarsCode AI刷题的过程中经验总结
这里给一道例题 : 0,1背包最大价值问题 - MarsCode
01背包问题
由题易得,
制约答案的条件有两个,前i个元素(数目限制)
体积限制 —> 二维状态 状态f[i][j]定义:前 i 个物品,背包容量 j下的最优解(最大价值)
通过对i个元素分析讨论有两种情况
当前背包容量够,可以选,因此需要决策选与不选第 i个物品:
选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。
我们的决策是如何取到最大价值,因此以上两种情况取 max()
所以f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
#include <iostream>
#include <algorithm>
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 = 1; 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;
}
优化版
分析得,由于在i的循环中,只涉及i和i-1的相对关系,可以用滚动数组
即数组的更新前和更新后可以表示两种状态
#include <iostream>
#include <algorithm>
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 = 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][j]优化到一维f[j],实际上只需要做一个等价变形
状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i]
ABAP复制代码
f[j] = max(f[j],f[j-v[i]]+w[i]);与f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
对比可知: 在新一轮的滚动过程中需要要要到的是上一轮的f[i]
(即此轮的f[i-1]),所以在更新f的时候需要倒序遍历才能保证是上一轮的f[i]
遍历顺序的理解
1先遍历背包还是物品 , 在求最大价值时并没有明显的区别 , 按照习惯先遍历物品即可
2当在别的场景下 , 就要自己根据题目进行判题了
参考视频 : 装满背包有多少种方法?518.零钱兑换II ,
先遍历物品求组合数
理解 : 先遍历物品时相当于固定了某一个物品 , 枚举所有的背包容量 , 在枚举第 i 个物品后 , 必定枚举 > i 的物品
所以物品的填放按照 i 从小到大的顺序 , 有序的枚举方案数且不重复即为组合数
先遍历背包容量求组合数
理解 : 先遍历容量则在每一容量下 for 循环可以任意的选择物品 , 物品的枚举出来的顺序并不一定有序 , 则枚举的元素有可能元素相同 ,而顺序不同 , 为排列数