1. 程式人生 > >Luogu 2569 [SCOI2010]股票交易 (樸素動規轉移 + 單調佇列優化)

Luogu 2569 [SCOI2010]股票交易 (樸素動規轉移 + 單調佇列優化)

題意:

已知未來 N 天的股票走勢,第 i 天最多買進 as [ i ] 股每股 ap [ i ] 元,最多賣出 bs [ i ] 股每股 bp [ i ] 元,且每天最多擁有 Mp 股,且每兩次交易至少需要相隔 W 天,求最多能賺多少錢。

細節:

1、初始情況下有無限的資金,但是不能算作賺的錢。
2、對於交易的定義為:買進一次股或者買出一次股,也就是不能在同一天進行兩種操作。

分析:

這題目根據題意不難發現它必定存在重複子結構,而且對於後面買賣股票的過程不會影響前方的收益情況,可以得到這可以用動態規劃解決。
狀態的構建也是顯然的:dp [ i ][ j ]

表示第 i 天擁有 j 股的最大價值。

1、初值->負無窮???
若是 0 則在轉移的時候將不會出現在沒有賺錢的情況下去買股票的情況。
且每個可行狀態 dp [ i ][ j ] = -ap [ i ] × j ( 0 ⩽ j ⩽ as [ i ] )

2、當前不買也不賣-> dp [ i ][ j ] = max ( dp [ i - 1 ][ j ], dp[ i ][ j ] )

3、當前需要買進股票,由於有 W 天間隔的限制,所以不難發現我們需要列舉當前這次貿易需要從哪一次交易轉移而來,但是由於有第二類情況的存在,其實第 0 至 i - W - 1

這幾天的交易最優值是單調不降的,所以最優的價值一定可以從 i - W - 1 天的狀態轉移而來,所以轉移:
dp [ i ][ j ] = max ( dp [ i ][ j ] , dp[ i - W - 1 ][ k ] - ( j - k ) × ap [ i ] )
( max ( 0 , j - as [ i ] ) ⩽ k ⩽ j )

4、當前需要賣出股票,轉移就是:
dp [ i ][ j ] = max ( dp [ i ][ j ] , dp[ i - W - 1 ][ k ] + ( k - j ) × bp [ i ] )
( j ⩽ k ⩽ min( Mp , j + bs [ i ] ) )

成功的寫出了樸素的動態規劃方程,現在考慮優化,由於 N ≤ 2000Mp ≤ 2000 這個 2D / 1D 的動態規劃顯然不能 AC ,比如把 3 的式子進行轉化:
dp [ i ][ j ] = max { dp [ i - W - 1][ k ] + k × ap [ i ] } - j × ap [ i ]
顯然又轉化成一個定區間求最值的模型,就可以使用單調佇列解決了,4 式的轉化同理可得,在轉移時可以令 jMp 開始往 0 進行轉移,按序加入佇列中。

程式碼:

#include<bits/stdc++.h>
#define Inf 1000000000
#define MAXN 2005
using namespace std;

int f[MAXN][MAXN], ap[MAXN], bp[MAXN], as[MAXN], bs[MAXN];
int n, Mp, w, que1[MAXN], que2[MAXN];

int main(){
    scanf("%d%d%d", &n, &Mp, &w);
    for (int i=1; i<=n; i++) scanf("%d%d%d%d", &ap[i], &bp[i], &as[i], &bs[i]);
    for (int i=0; i<=n; i++)
        for (int j=0; j<=Mp; j++) f[i][j]=-Inf;
    for (int i=1; i<=n; i++)
        for (int j=0; j<=as[i]; j++) f[i][j]=-j*ap[i];
    for (int i=1; i<=n; i++){
        for (int j=0; j<=Mp; j++) f[i][j]=max(f[i][j], f[i-1][j]);
        int head1=1, head2=1, tail1=0, tail2=0;
        for (int j=0; j<=Mp; j++){
            while (head1<=tail1 && que1[head1]<max(j-as[i], 0)) ++head1;
            if (head1<=tail1) f[i][j]=max(f[i][j], f[max(0, i-w-1)][que1[head1]]-(j-que1[head1])*ap[i]);
            while (head1<=tail1 && f[max(i-w-1, 0)][que1[tail1]]+que1[tail1]*ap[i]<=f[max(i-w-1, 0)][j]+j*ap[i]) --tail1;
            que1[++tail1]=j;
        }
        for (int j=Mp; j>=0; j--){
            while (head2<=tail2 && que2[head2]>min(Mp, j+bs[i])) ++head2;
            if (head2<=tail2) f[i][j]=max(f[i][j], f[max(0, i-w-1)][que2[head2]]+(que2[head2]-j)*bp[i]);
            while (head2<=tail2 && f[max(i-w-1, 0)][que2[tail2]]+que2[tail2]*bp[i]<=f[max(0, i-w-1)][j]+j*bp[i]) --tail2;
            que2[++tail2]=j;
        }    
    }
    int Ans=-Inf;
    for (int i=0; i<=Mp; i++) Ans=max(Ans, f[n][i]);
    printf("%d\n", Ans);
    return 0;
}