1. 程式人生 > >[BZOJ 1855][SCOI 2010]股票交易(單調佇列優化DP)

[BZOJ 1855][SCOI 2010]股票交易(單調佇列優化DP)

題目連結

思路

很顯然是個DP題,比較容易想到下面的DP做法:
f[i][j]表示第i天,手上有j個股票的最大獲利。顯然最終的答案為max{f[i][0]}(顯然以某天交易結束後收手不幹,肯定是手上沒有股票是最優的),DP初始化如下:

f(x)={f[i][j]=max{f[i1][j],apj},j<=as(i)f[i][j]=f[i1][j],j>as(i)

而DP方程則為(以轉移情況為購入股票舉例)

f[i][j]=max{f[iw1][k]+ap(jk)}
這樣做的正確性是顯然的,f
[i][j]
一定是前i天裡手持j股的最優解,這樣一來我們就節省了一維時間複雜度,但是還是會TLE。

變化一下這個DP方程:

f[i][j]=max{f[iw1][k]apk+apj}

一般可以用單調佇列優化的DP都能滿足形如f[x]=max(min){f[k]}+g[x]的形式,那麼我們可以把這裡的f[iw1][k]看作是上面的f[k]apj看作是上面的g[x],於是就可以從小到大列舉j,並用單調佇列維護二元組(k,f[iw1][k]),並保證隊首的f[iw1][k]是最大的,DP到某個j時,先把隊首所有不合法的f[iw1][k]全部彈出,然後用第一個合法的隊首去更新f

[i][j],然後將這時的f[iw1][j]放入隊尾,並同時維護佇列單調性。

而賣出的做法也非常相似,在這裡就不再贅述了

程式碼

剛開始陣列開大了,直接卡成TLE。。。鬱悶。。。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 2100
#define INF 0x3f3f3f3f

using namespace std;

pair<int,int
>q[MAXN]; int f[MAXN][MAXN]; int T,maxP,w; int main() { int ans=-INF; memset(f,-INF,sizeof(f)); scanf("%d%d%d",&T,&maxP,&w); for(int i=1;i<=T;i++) { int ap,bp,as,bs; scanf("%d%d%d%d",&ap,&bp,&as,&bs); for(int j=0;j<=as;j++) f[i][j]=-ap*j; //初始化f[i][j]=-ap*j(光買了股票) for(int j=0;j<=maxP;j++) f[i][j]=max(f[i][j],f[i-1][j]); //初始化什麼都沒買的情況更新f陣列 int k=i-w-1; //單調佇列裡儲存的就是(j,f[w][j]+ap[i]*j(-bp[i]*j))這個二元組 if(k>=0) { int h=0,t=0; for(int j=0;j<=maxP;j++) { //買入股票 while(h<t&&q[h].first<j-as) h++; while(h<t&&q[t-1].second<=f[k][j]+ap*j) t--; q[t++]=make_pair(j,f[k][j]+ap*j); if(h<t) f[i][j]=max(f[i][j],q[h].second-ap*j); } h=0,t=0; for(int j=maxP;j>=0;j--) { //賣出股票 while(h<t&&q[h].first>j+bs) h++; //!!!!! while(h<t&&q[t-1].second<=f[k][j]+bp*j) t--; q[t++]=make_pair(j,f[k][j]+bp*j); if(h<t) f[i][j]=max(f[i][j],q[h].second-bp*j); } } ans=max(ans,f[i][0]); //顯然以某天交易結束後收手不幹,肯定是手上沒有股票是最優的 } printf("%d\n",ans); return 0; }