Acwing6多重揹包Ⅲ_多重揹包+單調隊列優化

題目鏈接: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章