哈喽哈喽这里是小菜不拖延博主,本篇文章主要是重温一下01背包问题(实在是忘记了,又老是遇到),重新温习又有一些疑惑的地方。大佬可以划走了~希望可以帮助到你(本篇主要基于acwing进行学习)
原题连接:2. 01背包问题 - AcWing题库
二维基础代码
- 情况一:不选第i个物品,价值就是前i-1物品时的价值
- 情况二:剩余空间充足时,取选和不选时的最大价值
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
//n为物品数量,m为总体积
int n,m;
//v是体积,w是价值
int v[N],w[N];
//这里声明变量之后值都默认为1,f:选取i个物品,体积为j时的最大价值
int f[N][N];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
//这里i的含义是每次选取前i个物品,比如i=1,选到第1个物品(包括1)
for(int i=1;i<=n;i++)
{
//选取前i个物品,体积为j时,总价值,j不能超过m总体积
for(int j=0;j<=m;j++)
{
//情况一:不选第i个物品,价值为前i-1个时的值,
f[i][j]=f[i-1][j];
//情况二:假如剩余体积够,可选当前物品,比较选与不选时的最大价值
//如何理解f[i-1][j-v[i]]+w[i]?
//比如现在是第2件物品,那么我们是在选了前一件物品的情况下,再选第二件
//也就是f[2-1](第1件物品)[j-v[i]](选了第二件,所以减去第二件的体积),最后加上第二件的价值
//如果你觉得应该是:f[i][j-v[i]]+w[i],那就是你默认已经选了第i件了,在选了第i件物品上,又减去他的体积,显然是不合理的,你都选过了为什么现在还要减去这个
if(j>=v[i]) f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
//也可以写成
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;
}
function knapsack(n, m, items) {
let f = Array(n+1).fill().map(() => Array(m+1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 0; j<=m; j++) {
f[i][j]=f[i-1][j]
if(j>=items[i][0]) f[i][j] = Math.max(f[i-1][j], f[i-1][j - items[i][0]] + items[i][1]);
}
}
return f[n-1][m];
}
// 示例输入
let n = 4;
let m = 5;
let items = [
[0,0],
[1, 2], // 物品1: 重量 1,价值 2
[2, 4], // 物品2: 重量 2,价值 4
[3, 4], // 物品3: 重量 3,价值 4
[4, 5] // 物品4: 重量 4,价值 5
];
console.log(knapsack(n, m, items)); // 输出最大价值
疑惑点总结
为什么是f[i-1][j-v[i]]+w[i],而不是f[i][j-v[i]]+w[i]
比如现在是第2件物品,那么我们是在选了前一件物品的情况下,再选第二件,也就是f[2-1] (第1件物品)[j-v[i]] (选了第二件,所以减去第二件的体积),最后加上第二件的价值。如果你觉得应该是:f[i][j-v[i]]+w[i],那就是你默认已经选了第i件了,在选了第i件物品上,又减去他的体积,显然是不合理的,你都选过了为什么现在还要减去这个
优化一维代码
#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];
return 0;
}
function knapsack(n, m, items) {
let f = Array(m + 1).fill(0);
for (let i = 1; i <= n; i++) {
let [weight, value] = items[i];
for (let j = m; j >=weight; j--) {
f[j] = Math.max(f[j], f[j - weight] + value);
console.log(f,j,weight)
}
}
return f[m];
}
// 示例输入
let n = 4;
let m = 5;
let items = [
[0,0],
[1, 2], // 物品1: 重量 1,价值 2
[2, 4], // 物品2: 重量 2,价值 4
[3, 4], // 物品3: 重量 3,价值 4
[4, 5] // 物品4: 重量 4,价值 5
];
console.log(knapsack(n, m, items)); // 输出最大价值
二维到一维理解
其实就是等于去掉一层:
f(i)[j]=max(f(i)[j],f(i)[j-v[i]]+w[i]);
ok此时我们就会发现,假如他们都是i,一起减去没有问题,可是原本的代码是f[i-1]不是f[i],所以直接去掉等于又重新基于i的状态减去了i的体积(参考上述疑惑点总结的位置),也就是这个物品被选了不止一次,所以不是对的
这里更改了体积的遍历,变成从大到小遍历,j-v[i]一定小于j,所以j-v[i]一定没有被算过
为什么从二维到一维就需要,体积从大到小遍历?
从运行结果来理解
其实盯着代码看很难理清楚的话,最好的方法就是先利用打印搞清楚原理(是的我就是consolelog的忠实教徒),首先我们可以看到二维数组时,我们从左到右,依次确立结果
二维数组f的输出:
一维数组按照体积从小到大时f的输出:
for (let i = 1; i <= n; i++) {
let [weight, value] = items[i];
for (let j = 0; j <= m; j++) {
if(j>=weight) f[j] = Math.max(f[j], f[j - weight] + value);
console.log(f,j,weight)
}
}
一维数组按照体积从大到小时f的输出:
每次我们都要保证j-v[i]这个状态没有被算过,否则观察第二图,当被算过时也就是这个物品被拿过,就会重复叠加,无法得出正确结果。
原来的二维逻辑是,始终坚持i来自于i-1,i-1这一部分没有被算过,现在化为一维数组,没有被选过的任务给到了j-v[i],j来自于j-v[i],j>j-v[i]. 如下图,正序计算会出现j-v[i]被计算过,也就是被拿过的情况,和我们的出发点是不一样的。