单调队列优化版多重背包

代码

/*
思路来源:
f(i, j) = max(f(i - 1, j), f(i - 1, j - v) + w, f(i - 1, j - 2 * v) + 2 * w, ..., f(i - 1, j - s * v) + s * w)
f(i, j - v) = max(         f(i - 1, j - v)    , f(i - 1, j - 2 * v) + w,          f(i - 1, j - s * v) + (s - 1) * w + f(i - 1, j - (s + 1) * v) + s * w)
......
f(i, j - s * v) = max(f(i - 1, j - s * v), ...)
这不就是窗口大小为 s 的滑动窗口吗
*/
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 20010;

int n, m;
int f[N], g[N], q[N];

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) {
        int v, w, s;
        scanf("%d%d%d", &v, &w, &s);
        memcpy(g, f, sizeof f);
        for(int j = 0; j < v; j ++) { // 序列的起点
            // f(0), f(0 + v), f(0 + 2 * v) + ...
            // f(1), f(1 + v), f(1 + 2 * v) + ...
            // f(j), f(j + v), f(j + 2 * v) + ...
            // 0 <= j < v
            int hh = 0, tt = -1;
            for(int k = j; k <= m; k += v) { // 枚举序列
                // 序列下标是 j, j + v, j + 2 * v, ...
                // 序列值为 f(j), f(j + v) - w, f(j + 2 * v) - 2 * w
                // 当前枚举的下标是 k
                if(hh <= tt && q[hh] < k - s * v) hh ++; 
                // 上一步的含义是,因为窗口是从 k 往前 s 个元素,因此要判断是否在窗内
                if(hh <= tt) f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w);
                // 举个例子,f(i, j + s * v) = 
                // = max(f(i - 1, j) + s * w, f(i - 1, j + v) + (s - 1) * w, ..., f(i - 1, j + s * v))
                // = max(f(i - 1, j), f(i - 1, j + v) - w, ..., f(i - 1, j + s * v) - s * w) + s * w
                while(hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt --;
                // 队列是单调递减的
                q[++ tt] = k;
            }
        }
    }
    printf("%d
", f[m]);
    return 0;
}
原文地址:https://www.cnblogs.com/miraclepbc/p/14460159.html