P2569 股票交易
題目大意:
你初始時有∞ 元錢,並且每天持有的股票不超過 Maxp 。 有 T 天,你知道每一天的買入價格( AP[i] ),賣出價格( Bp[i] ), 買入數量限制( AS[i] ),賣出數量限制( BS[i] )。 並且兩次交易之間必須間隔 W 天。 現在問你 T 天結束後,最大收益是多少。
分析:
註意對題意的理解,雖然有無限的錢,但是買股票是要減少當前的收益的,收益是賣出的總錢數減去買入的錢數。收益並不是只增不減。
可以想到的是:動態規劃。設f[i][j]表示前i天之後,剩下j股股票的最大收益。顯然根據題意,在j相同的時候,i越往後的時候,f[i][j]越大。
狀態轉移方程,買入和賣出是相同的思路:
賣出:
f[i][j]=max(f[i-w-1][k]+(k-j)×bp[i]) (j<=k<=j+bs[i])
買入:
f[i][j]=max(f[i-w-1][k]-(j-k)×ap[i]) (j-as[i]<=k<=j)
這種情況的復雜度是O(T×Mp×Mp)直接T掉。
將轉移方程括號展開,發現:
f[i-w-1][k]+(k-j)×bp[i]=(f[i-w-1][k]+k×bp[i])-j×bp[i]
在給定i,j的時候,唯一在變的變量就是k,k的取值會影響最大值。而且給予j正確的循環順序,k的取值區間是逐漸在平移的。
想到了什麽?
滑動窗口!單調隊列!單調隊列優化DP!
我們外層循環i,內層循環j,每次先除去過期的解,加上新的選擇,再從單調隊列隊頭取出最優解,進行更新。
註意:
1.在買入和賣出時,為了保證能轉移德到j的所有元素都在隊列裏,j的循環順序是不同的。
2.對於給定的i,處理買入賣出的順序可以顛倒。無所謂。
3.當i-w-1小於0時,取0即可。
代碼:
#include<bits/stdc++.h> using namespace std; const int N=2000+10; int t,mp,w; int f[N][N]; int ans; int q[N],hd=1,tl=0; int main() { scanf("%d%d%d",&t,&mp,&w); int ap,bp,as,bs;//並不需要數組 memset(f,0xcf,sizeof f);//初值負無窮 f[0][0]=0; for(int i=1;i<=t;i++) { scanf("%d%d%d%d",&ap,&bp,&as,&bs); hd=1,tl=0; int from=max(0,i-w-1);//從何轉移 for(int j=mp;j>=0;j--)//賣出 { f[i][j]=max(f[i][j],f[i-1][j]);//可以今天不買不賣 while(hd<=tl&&(j+bs<q[hd])) hd++;//除去過期者 while(hd<=tl&&(f[from][q[tl]]+q[tl]*bp<f[from][j]+j*bp)) tl--;//因為從i的上一個狀態來,所以先加入新元素 q[++tl]=j; f[i][j]=max(f[i][j],f[from][q[hd]]+q[hd]*bp-j*bp); } hd=1,tl=0; for(int j=0;j<=mp;j++)//買入 { f[i][j]=max(f[i][j],f[i-1][j]); while(hd<=tl&&(j-as>q[hd])) hd++; while(hd<=tl&&(f[from][q[tl]]+q[tl]*ap<f[from][j]+j*ap)) tl--; q[++tl]=j; f[i][j]=max(f[i][j],f[from][q[hd]]+q[hd]*ap-j*ap); } } for(int j=0;j<=mp;j++) ans=max(ans,f[t][j]); printf("%d",ans); return 0; }
總結:
1.對於決策轉移時,有明顯單調性的情況,對於狀態移動時,轉移決策重復度較大。都可用單調隊列優化。
2.單調隊列利用每個元素只能進一次,出一次,使得O(n)變為均攤O(1)復雜度,簡化時間。
P2569 股票交易