題目鏈接:https://www.acwing.com/problem/content/6/
解題過程:
首先原始的多重揹包問題的最原始的做法是直接開三重循環,然後進一步優化是通過二進制將其轉換成0/1揹包去做,這裏我用的方法是通過單調隊列去優化原始的三重循環,時間複雜度爲0(n * m);這道題目是:2e7,不會超時。
首先我們需要知道這樣的算式:
f[i, j] = max(f[i - 1, j], f[i - 1, j - v]+w, f[i - 1, j - 2v]+2w, … , f[i - 1, j - sv]+sw)
f[i, j - v] = max( , f[i - 1, j - v], f[i - 1, j - 2v]+ w, …, f[i - 1, j- sv]+(s - 1)w, f[i - , j - (s+1)v]+sw)
當我們使用原始的做法的時候,對於每一個f[i - kv], 我們是直接通過遍歷的方法去求其最大數值,通過單調隊列到達的目的就是將這一重循環給去掉,通過上面的算式,我們發現,對於f[i, j]來說,其實有很大一部分與f[i, j - v]是重複的,他們的差距僅僅只是相差一個w, 這個是可以通過相減來處理掉的,並且由於是求最大數值,所以剛好想到可以用單調隊列來寫。
代碼:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 20005;
int f[maxn], g[maxn], q[maxn];
/*
f[k]:f[i, k]
g[k]:f[i - 1, k]
q[]:是一個數組模擬的單調隊列
*/
int main(void) {
int n, m; 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); //每次都將f複製道g中,這樣在計算f[i, k]地時候,就可以直接調用g數組
//這裏採用的方法是,遍歷所有可能的餘數來進行dp;與原始的遍歷方法有一點理解上的細微差別
for(int j = 0; j < v; j ++) {
int hh = 0, tt = -1; //hh:隊頭, tt:隊尾
for(int k = j; k <= m; k += v) {
//判斷當前隊頭元素是否已經出列,注:隊列中存儲的是體積,相當於下標
if(hh <= tt && k - s * v > q[hh]) hh ++;
//在隊列不爲空的前提下,判斷新進入的元素與隊尾元素進行比較,若是前者更大,則將隊尾元素出隊
//爲什麼這裏是 - 號,原因:我們可以去看寫出的算式,發現越是體積越大的時候,其實越後面纔會加一個w,
//剛好構成了一個等差數列,那麼我們如果通過這個相對w之差,則剛好等價於原來相加的情況
while(hh <= tt && g[q[tt]] - q[tt] / v * w <= g[k] - k / v * w) tt --;
//入隊
q[++ tt] = k;
//將隊頭元素放進f中,隊頭元素:此時的最大數值
f[k] = g[q[hh]] + (k - q[hh]) / v * w;
}
}
}
printf("%d\n", f[m]);
return 0;
}