1. 程式人生 > >【筆記篇】單調隊列優化dp學習筆記&&luogu2569_bzoj1855股票交♂易

【筆記篇】單調隊列優化dp學習筆記&&luogu2569_bzoj1855股票交♂易

打表 交易 賣出 .... while 變量 計算 原則 spa

DP頌

DP之神 聖潔美麗 算法光芒照大地
我們懷著 崇高敬意 跪倒在DP神殿裏
你的復雜 能讓蒟蒻 試圖入門卻放棄
在你光輝 照耀下面 AC真心不容易

dp大概是最經久不衰 亙古不化的算法了吧.
而且有各種各樣的類型 優化之類的.
一直dp都不怎麽好. 而且也不太知道應該怎麽提高.
基本見到不認識的dp方程就不大會推(但我會打表啊= =

所以dp還是很有的學的~

正好最近剛剛肝了計算幾何, 所以就順帶搞一下斜率優化dp一類的...

單調隊列優化dp

單調隊列大家都會吧?
不會的先出去學一下, 這裏不講.


好的, 我們來看一下這個柿子
\[ f[i]=max\{f[j]+\omega(j)\} (j\in[1..i)) \]

其中\(\omega(j)\)是一個費用函數, 一般會根據題目的不同而變化.

這個dp能做到什麽復雜度呢?
首先一眼\(O(n^2)\)...
然而我們可以用一個變量記錄一下之前出現過的最大值.
這樣轉移是\(O(1)\)的了, 總復雜度就降到了\(O(n)\).

但是如果是這樣呢?
\[ f[i]=max\{f[j]+\omega(j)\}(j\in[i-m,i)) \]

那就不能只維護一個變量了, 因為最大值如果出現在\(j\)的取值區間之外則轉移是不合法的.
這樣我們就考慮用單調隊列來維護最大值, 這樣轉移依然可以做到\(O(1)\), 總復雜度\(O(n)\).

看道題: (woc辣雞bzoj給的什麽zz數據範圍, T都沒給怎麽做...)

這題可以寫出這麽一個狀態轉移方程
\[ 令f[i][j]表示第i天擁有j支股票的最大收益, \f[i][j]=max\left\{ \begin{matrix} f[i-w-1][k]-ap[i]*(j-k), (k\in[j-as[i],j)) //買入\f[i-w-1][k]+bp[i]*(k-j),(k\in(j,j+bs[i]]) //賣出\f[i-1][j]//不交♂易 \end{matrix} \right. \]

其中不交易的情況好處理, 但是如果前面兩種枚舉\(k\)的話就要做到\(O(n^*maxP^2)\), 顯然是過不了的, 我們必須考慮優化.
我們以買入為栗化一波柿子(因為賣出同理) :

\[ f[i][j]=max\{f[i-w-1][k]-ap[i]*(j-k)\}\ =max\{f[i-w-1][k]+ap[i]*k\}-ap[i]*j (k\in[j-as[i],j]) \]
我們令\(\omega(x)=ap[i]*x\), 而我們枚舉\(i\), 就可以視為\(i\)是定值, 於是\(ap[i],as[i]\)都是定值.
我們就可以看出第二維形成了一個能用單調隊列優化的柿子了.
這樣優化之後復雜度成功降到了\(O(n*maxP)\), 就可以通過此題了.

根據貪心原則, 為了獲得最多的現金, 手裏不應該留股票, 所以用每個\(f[i][0]\)更新答案即可.

不過要註意一下邊界條件... 挺扯淡的..

代碼:

#include <cstdio>
#include <cstring>
const int N=2020;
const int INF=0x7fffffff;
int q[N<<1],d[N<<1],h=1,t=0;
int f[N][N],ap[N],bp[N],as[N],bs[N];
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar())a=a*10+c-48;return a;
}
inline int max(const int& a,const int& b){
    return a>b?a:b;
}
int main(){
    int n=gn(),m=gn(),w=gn(),ans=0;
    for(int i=1;i<=n;++i)
        ap[i]=gn(),bp[i]=gn(),as[i]=gn(),bs[i]=gn();
    memset(f,192,sizeof(f));
    for(int i=1;i<=n;++i){
        for(int j=0;j<=as[i];++j) f[i][j]=-ap[i]*j;
        for(int j=0;j<=m;++j) f[i][j]=max(f[i][j],f[i-1][j]);
        if(i>w){
                h=1; t=0;
            for(int j=0;j<=m;++j){              
                int val=f[i-w-1][j]+j*ap[i];
                while(t>=h&&val>=q[t]) --t;
                q[++t]=val; d[t]=j;
                while(t>=h&&d[h]<j-as[i]) ++h;
                f[i][j]=max(f[i][j],q[h]-ap[i]*j);
            }
            h=1; t=0;
            for(int j=m;j>=0;--j){
                int val=f[i-w-1][j]+bp[i]*j;
                while(t>=h&&val>=q[t]) --t;
                q[++t]=val; d[t]=j;
                while(t>=h&&d[h]>j+bs[i]) ++h;
                f[i][j]=max(f[i][j],q[h]-bp[i]*j);
            }
        }
        ans=max(ans,f[i][0]);
    }
    printf("%d",ans);
}

反正差不多就這樣吧....

【筆記篇】單調隊列優化dp學習筆記&&luogu2569_bzoj1855股票交♂易