零一背包
题目描述
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
DP_二维数组代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1001;
int n,m;
int v[N],w[N],f[N][N]={0};
int main()
{
cin >> n>> m;
for(int i=1;i<=n;i++)
cin >> v[i] >> w[i];
// f[i][j] 表示从前 i 个物品里面选择体积不超过 j 的最大价值
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++){
// 不选择第 i 个物品
f[i][j] = f[i-1][j];
// 选择第 i 个物品
if(j >= v[i])
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
int res = 0;
for(int i=0;i<=m;i++)
res = max(res,f[n][i]);
cout << res << endl;
return 0;
}
注意:其中,核心代码部分
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
// 等价于:
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
优化: 由于核心代码部分的两层循环中,内层循环里面要更新f [i] [j] ,只与上一层的 f [i-1] [j]有关,所以不用记录整一层。优化方法:只采用一维数组(另一种优化方式:滚动数组)。
DP_一维数组代码
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++){
// 1. f[i][j] = f[i-1][j]; 由于是一维的,暂且视作只与j有关,所以与第一层没有关系,这一行可以直接注释
// 3. 直接将判断条件转移到 for 循环里
if(j >= v[i]){
//f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
// 2. 尽量做等价变换
// 如果直接改为 f[j] = max(f[[j],f[j-v[i]]+w[i]);
// 其实等价于 f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
// 而上面提到了 f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]) 等价于...max(f[i][j],...)
// 所以 式子中只有 max 函数里的第二项 f[j-v[i]]+w[i] 不是等价变换的
// 想要做等价变换,现在就需要想办法让 f[j-v[i]] 代表原来的 f[i-1][j-v[i]] 而不是 f[i][j-v[i]]
/* 实现思路:内层循环用逆序,才能使得 f[j-v[i]]是上一层没有被更新的(j-v[i]肯定小于j,
而内层循环 逆序 表明 f[j] 肯定比 f[j-v[i]] 要先被修改,故f[j-v[i]]是未被修改的值,
也就是说,f[j-v[i]]是上一层的值,即等价于原来的f[i-1][j-v[i]])*/
}
}
优化后的最终代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1001;
int n,m;
int v[N],w[N],f[N][N]={0};
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=v[i];j>=m;j--)
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout << f[m] << endl;
return 0;
}
说明: 核心代码部分,按照上述代码注释中的1,2,3步骤进行更改,最终结果即为优化结果
步骤2:尽量做等价变换
1. 如果直接改为 f[j] = max(f[[j],f[j-v[i]]+w[i]);
2. 其实等价于 f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
3. 而上面提到了 f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]) 等价于...max(f[i][j],...)
4. 所以 式子中只有 max 函数里的第二项 f[j-v[i]]+w[i] 不是等价变换的
5. 想要做等价变换,现在就需要想办法让 f[j-v[i]] 代表
原来的 f[i-1][j-v[i]] 而不是 f[i][j-v[i]]
6. 实现思路:内层循环用逆序,才能使得 f[j-v[i]]是上一层没有被
更新的(j-v[i]肯定小于j,而内层循环 逆序 表明 f[j] 肯定
比 f[j-v[i]] 要先被修改,故f[j-v[i]]是未被修改的值,也就是说,
f[j-v[i]]是上一层的值,即等价于原来的f [i-1] [j-v[i]])
参考:零一背包_一维数组