1. 程式人生 > >P2569 股票交易

P2569 股票交易

color 間隔 題意 狀態轉移方程 滑動窗口 影響 turn 決策 選擇

題目大意:

你初始時有∞ 元錢,並且每天持有的股票不超過 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 股票交易