为了以免自己遗忘这种优化方法的思想,所以写这篇博客当做笔记。
01背包问题的朴素版解
#include<iostream>
using namespace std;
const int N = 1010;
int n , s;
int v[N] , w[N];
int f[N][N];
int main()
{
cin >> n >> s;
for(int i = 1 ;i <= n; i++ ) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i++)
for(int j = 0; j <= s; 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][s] << endl;
return 0;
}
首先我们注意到在状态转移的过程中,我们始终只用到了 f[i] 和 f[i-1] 这两层,i-1层 储存的数据可以看成是在循环的上一次计算的量,即这一次循环的 f[i-1] 数据是上一次循环中 f[i] 的数据。
因此我们只需要关注循环层数之间数据的计算。于是我们直接去掉第一维,那么此时 f[i][j] = f[i-1][j] 改成 f[j]=f[j] ,这里显然是成立的,因为我们刚刚说到 f[i-1] 是上一次循环计算的 f[i] 也就是说右边的 f[j] 其实是上一次循环计算的 f[j] ,当前循环的 f[j] 等于上一层循环的 f[j] 等价于 f[i][j] = f[i-1][j] ,因此这一行变成一个恒等式,我们删掉。
接下来我们来尝试修改第二行的代码,此时若全部去掉第一维就是 f[j] = max(f[j],f[j-v[i]]+w[i]) ,这个式子是和原式不等价的,因为我们的 j 变量是从小到大遍历,因此 f[j-v[i]] 也是从小到大遍历,设 j-v[i] = t , 也就是 f[t] 中的t是一个比 j 小的值,也就是说它会在当前循环层中被当成 f[j] 的时候更新了数据,那么 f[j-v[i]] 应该等价于 f[i][j-v[i]] , 是本层循环更新的一个量而不是我们需要的 f[i-1][j-v[i]] 上一层循环中的一个量了。
那么该如何修改呢,我们注意到如果我们从大到小遍历j的话,在使用f[t]的时候就会因为t还没有更新数据而还存着上一层循环的变量,也就是f[i-1][t],所以只需要改掉循环的顺序,就可以实现一维优化了。
01背包一维优化
#include<iostream>
using namespace std;
const int N = 1010;
int n , s;
int v[N] , w[N];
int f[N];
int main()
{
cin >> n >> s;
for(int i = 1 ;i <= n; i++ ) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i++)
for(int j = s; j >= v[i]; j--)
{
f[j] = max(f[j] , f[j-v[i]]+w[i]);
}
cout << f[s] << endl;
return 0;
}