洛谷P1776 寶物篩選_NOI導刊2010提升(02)(多重揹包,單調隊列)

爲了學習單調隊列優化DP奔向了此題。。。學習

基礎的多重揹包就不展開了。設\(f_{i,j}\)爲選前\(i\)個物品,重量不超過\(j\)的最大價值,\(w\)爲重量,\(v\)爲價值(蒟蒻有強迫症,特別不喜歡把\(v\)\(w\)反着搞,\(weight\)\(value\)嘛!),直接給轉移方程優化

\[f_{i,j}=\max\{f_{i-1,j-kw_i}+kv_i\},k\in[0,min\{m,\lfloor \frac j{w_i}\rfloor\}]\]spa

顯然,\(f_i\)都是從\(f_{i-1}\)轉過來的,因此第一維能夠滾掉,獲得每次轉移的更簡化的方程.net

\[f_j=\max\{f_{j-kw}+kv\}\]code

這樣是\(O(nmW)\)的,仍是要想辦法優化。blog

衆所周知,DP優化的根本原則是去掉無用的狀態、利用重複轉移的狀態。但是這方程一眼根本看不出什麼可優化的地方啊。。。。。。隊列

咱們要像這位Dalao同樣善於發現,他的blogget

因此,無論這個想法是怎麼來的,咱們先把\(j\)按模\(i\)意義下分組,設\(j=k_1w+d\),那麼一組裏的\(d\)都是同一個值。io

而後方程就變成了這樣class

\[f_j=\max\{f_{k_1w+d-kw}+kv\}\]

\[\qquad\qquad\quad=\max\{f_{(k_1-k)w+d}-(k_1-k)v\}+k_1v\]

忽然看到了\(k_1-k\)的重複出現!這也就意味着,在每一組中,有意義的狀態只有\(\lfloor\frac{W-d}w\rfloor\)種!(\(W\)是最大載重)每次總的狀態也就只有\(O(W)\)了。

\(g_k=f_{kw+d}-kv\)。那麼由於有\(m\)的限制,因此對於每一個\(k_1\),咱們須要且只能從\(max\{g_k|k\in[\max\{0,k_1-m\},k_1]\}\)轉移。對於這樣的轉移,能夠形象地和滑動窗口聯繫一下,至關於有一個寬度爲\(m\)的窗口從一邊一步步往另外一邊移動,每移一次都要取出窗口內的最大值。這個就上單調隊列維護。

首先枚舉\(d\)。接着,爲了方便滾動,咱們從大到小枚舉\(k\)\(k_1\),用一個單調隊列維護下標在\([k_1-m,k_1]\)範圍內的依次遞減的若干個\(g\)值,由於顯然若是有\(g_x\geq g_y,x<y\)的話\(g_y\)是沒有用的。枚舉\(k_1\)時,每次隊首元素超出了範圍就把它出隊。用如今的隊首更新\(f_j\)\(f_{k_1w+d}\)。接着下一個元素\(g_{k_1-m-1}\)要入隊了,把隊尾\(g\)比這個小的全出隊,再讓它進來。最後輸出\(f_W\)便可。

這樣就是\(O(nW)\)的了,比二進制拆分難理解些可是更優秀了。

結合代碼理解會更輕鬆哦

#include<cstdio>
#define RG register
#define R RG int
#define G c=getchar()
const int N=1e5+9;
int f[N],g[N],q[N];
inline int in(){
    RG char G;
    while(c<'-')G;
    R x=c&15;G;
    while(c>'-')x=x*10+(c&15),G;
    return x;
}
inline int max(R x,R y){return x>y?x:y;}
inline void chkmx(R&x,R y){if(x<y)x=y;}
int main(){
    R n=in(),maxw=in(),maxk,lim,v,w,m,d,i,k,k1,h,t,now;
    for(i=1;i<=n;++i){
        v=in();w=in();m=in();
        for(d=0;d<w;++d){//枚舉餘數
            maxk=(maxw-d)/w;lim=max(maxk-m,0);//先肯定最初的範圍
            for(t=0,k=maxk-1;k>=lim;--k){//窗口先擴大寬度到m
                now=f[k*w+d]-k*v;
                while(t&&g[t]<=now)--t;//維護單調性
                g[++t]=now;q[t]=k;
            }
            for(h=1,k1=maxk;~k1;--k1,--k){//能夠開始轉移了
                if(h<=t&&q[h]>=k1)++h;//接着移動
                if(h<=t)chkmx(f[k1*w+d],g[h]+k1*v);//轉移
                if(k<0)continue;//注意窗口可能已經出正數範圍了
                now=f[k*w+d]-k*v;
                while(h<=t&&g[t]<=now)--t;//維護單調性
                g[++t]=now;q[t]=k;
            }
        }
    }
    printf("%d\n",f[maxw]);
    return 0;
}
相關文章
相關標籤/搜索