【bzoj1855】 [Scoi2010]股票交易
上一篇blog已經講了單調隊列與單調棧的用法,本篇將講述如何借助單調隊列優化dp。
我先丟一道題:bzoj1855
此題不難想出O(n^4)做法,我們用f[i][j]表示第i天手中持有j只股票時,所賺錢的最大值。
不難推出以下式子:
$f[i][j]=max\left\{
\begin{aligned}
f[k][l]+(l-j)\times bs[i] , l \in [j,j+bs[i]]\\
f[k][l]-(j-l)\times as[i] , l \in [j-as[i],j]\\
\end{aligned}
\right \}
k \in [1,i-w]$
考慮到第i天持有的j只股票不一定全是第i天購買的,則對於$\forall j$,有$f[i][j]≥f[i][j-1]$,式子可化為O(n^3),變為:
$f[i][j]=max\left\{
\begin{aligned}
f[i-w-1][l]+(l-j)\times bs[i] , l \in [j,j+bs[i]]\\
f[i-w-1][l]-(j-l)\times as[i] , l \in [j-as[i],j]\\
\end{aligned}
\right \}$
考慮到$i,j≤1000$,如采用此做法依然會TLE,我們考慮采用單調隊列進行優化,以下以賣出股票舉例:
我們設$k<l<j$,我們認為$f[i-w-1][k]$比$f[i-w-1][l]$優,則必然滿足$f[i-w-1][k]>f[i-1-1][l]+(k-l) \times bp[i]$。
我們對於每一個$i$,維護一個$f[i-w-1]$的單調隊列,采用上述的判定機制刪除非最優元素,同時考慮到$k,l$應位於區間$[j,j+bs[i]]$中,則需從隊頭刪除下標不位於該區間的元素,最優用隊頭元素更新f[i][j]即可。
買入同理。
1 #include<bits/stdc++.h> 2 #define M 4010 3 using namespace std; 4 int f[M][M/2]={0},ap[M]={0},bp[M]={0},as[M]={0},bs[M]={0}; 5 int t,n,w,head,tail,q[M]={0},id[M]={0}; 6 int main(){ 7 scanf("%d%d%d",&t,&n,&w); 8 for(int i=w+1;i<=t+w;i++) scanf("%d%d%d%d",ap+i,bp+i,as+i,bs+i); 9 for(int i=0;i<=w;i++) 10 for(int j=1;j<=n;j++) f[i][j]=-1234567890; 11 for(int i=w+1;i<=t+w;i++){ 12 for(int j=0;j<=n;j++) f[i][j]=f[i-1][j]; 13 head=tail=0; 14 for(int j=1;j<=n;j++){ 15 if(head<tail&&id[head+1]<j-as[i]) head++; 16 while(head<tail&&q[tail]-f[i-w-1][j-1]<((j-1)-id[tail])*ap[i]) tail--; 17 q[++tail]=f[i-w-1][j-1]; id[tail]=j-1; 18 if(head<tail) f[i][j]=max(f[i][j],q[head+1]-(j-id[head+1])*ap[i]); 19 } 20 head=tail=0; 21 for(int j=n-1;j>=0;j--){ 22 if(head<tail&&j+bs[i]<id[head+1]) head++; 23 while(head<tail&&f[i-w-1][j+1]-q[tail]>(id[tail]-(j+1))*bp[i]) tail--; 24 q[++tail]=f[i-w-1][j+1]; id[tail]=j+1; 25 if(head<tail) f[i][j]=max(f[i][j],q[head+1]+(id[head+1]-j)*bp[i]); 26 } 27 } 28 printf("%d\n",f[t+w][0]); 29 }
【bzoj1855】 [Scoi2010]股票交易