Bootstrap

完全背包问题一纬状态是怎么推导出的,结果为什么和01背包问题转移方程如此类似!

一、朴素解决完全背包问题

这个问题相信对于很多小伙伴来说不难,只是01背包问题的加强版,01背包只能选一个,完全背包可以选无数个,想解决这个问题并不难,只需要再嵌套一层循环,将每个选法的方案数纪录下来即可,代码实现如下。

#include <iostream>
using namespace std;

const int N = 1010;

int n, m;
int w[N], v[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];
            int cut = 0;
            for(int k = 0; k <= j; k += v[i])
            {
                f[i][j] = max(f[i][j],f[i - 1][j - k] + cut * w[i]);
                cut ++;
            }
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

但是,如果题目对于时间有要求,这种三重循环的做法未免太过于暴力,如果n为1000的话时间复杂度高达10^9,显然会超时,那么我们该如何优化呢?

二、拆解朴素完全背包的状态转移方程

不难看出,完全背包的状态转移方程可拆解为如下代码所示:

f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i], f[i][j - 2 * v[i]] + 2 * w[i],...)

而想要优化这个状态转移方程神奇的地方来了,我们将j + v[i] 带入到上述方程的 j 中等式依然成立,如下:

f[i][j - v[i]] = max(f[i - 1][j - v[i]], f[i - 1][j - 2 * v[i]] + w[i], ...)

请同学们自习观察上面两个等式,我们可以惊讶的发现,第一个上面等式右边只比下面等式多了一个 f[i - 1][j] + w[i],所以我们可以轻松的将第一个等式后面的一坨替换成第二个等式再加一个w[i],即

f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);

这时我们边可以将三重循环压缩成两重循环,而我们再仔细观察,这个状态转移方程所需要的数据只有f[i - 1][j]需要来自上层循环,而这个数据一维数组可以轻松记录,我们先尝试直接将第一纬去掉,如下

f[j] = max(f[j],f[j - v[i]] + w[i])

此时方程右边用到的f[j]刚好是还未被更新的f[j],而由于j - v[i]严格小于j,所以第i次循环时肯定被更新过,恰好,我们刚好需要第i层循环时的状态,我们便可以在不将体积倒序遍历的条件下将二维空间缩减成一维空间, (如果将体积倒序的话,f[j - v[i]]所记录的值便是第i - 1层循环所保存的值,等式将不会成立),与01背包问题的差别也在此体现,具体01背包问题可参考这篇文章为什么01背包问题可以从二维转换成一纬

三、最终完全背包问题的状态转移方程:

二维

#include <iostream>
using namespace std;

const int N = 1010;

int n, m;
int w[N], v[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 - 1][j], f[i][j -  v[i]] + w[i]);
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

一纬

#include <iostream>
using namespace std;

const int N = 1010;

int n, m;
int w[N], v[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 = 0; j <= m; j ++)
        {
            if(j >= v[i])
            f[j] = max(f[j], f[j -  v[i]] + w[i]);
        }
    }
    cout << f[m] << endl;
    return 0;
}

;