1. 程式人生 > 其它 >多重揹包問題 — 單調佇列優化

多重揹包問題 — 單調佇列優化

目錄

問題描述

\(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;
}