多重揹包問題 — 單調佇列優化
阿新 • • 發佈:2021-08-29
目錄
問題描述
有 \(N\) 種物品和一個容量是 \(V\) 的揹包。
第 \(i\) 種物品最多有 \(s_i\) 件,每件體積是 \(v_i\),價值是 \(w_i\)。
求解將哪些物品裝入揹包,可使物品體積總和不超過揹包容量,且價值總和最大。
輸出最大價值。
輸入格式
第一行兩個整數\(N,V (0<N≤1000, 0<V≤20000)\),用空格隔開,分別表示物品種數和揹包容積。
接下來有 \(N\) 行,每行三個整數 \(v_i,w_i,s_i\),用空格隔開,分別表示第 \(i\) 種物品的體積、價值和數量。
輸出格式
輸出一個整數,表示最大價值。
輸入樣例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
輸出樣例:
10
Code
#include <iostream> #include <cstring> #include <algorithm> using namespace std; constexpr int N = 20020; /* * 由於只會用到兩種狀態, 所以用 * f[]代表當前狀態, g[]代表上一個狀態 */ int f[N], g[N]; int n, m; /* * 單調佇列q[]儲存的是r + k * v, 即揹包的狀態. * 而維護的卻是當前揹包狀態下的價值的單調性. * 視窗長度是當前所能拿走物品的最大數量s. */ int q[N]; int main() { scanf("%d%d", &n, &m); for (int i = 0; i < n; i++) { int v, w, s; scanf("%d%d%d", &v, &w, &s); memcpy(g, f, sizeof f); // r代表餘數 for (int r = 0; r < v; r++) { int hh = 0, tt = -1; // k代表拿走物品的體積數 for (int k = r; k <= m; k += v) { /* * k - q[hh]代表當前物品放入揹包中的體積, * 之後再除v就可以得到當前物品的數量, * 如果 >s, 說明超出視窗的值, 彈出隊頭. */ if (hh <= tt && (k - q[hh]) / v > s) hh++; // 更新如果能取該k個該物品的最大價值 if (hh <= tt) f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w); /* * r + k * v = q[tt], 所以(q[tt] - r) / v * w = k * w, 即拿走的物品的價值. * 由於每一個狀態之間都有偏移量, 所以進行比較前需要刪掉偏移量, * 之後比較彈出隊尾元素, 保證佇列的單調性 */ while (hh <= tt && g[q[tt]] - (q[tt] - r) / v * w <= g[k] - (k - r) / v * w) --tt; // 加入新的元素 q[++tt] = k; } } } cout << f[m] << endl; return 0; }