HDU_3401 Trade 单调dp

http://acm.hdu.edu.cn/showproblem.php?pid=3401

做了一天的一道题目,一开始想复杂了,或者本来就没有搞清楚 。。。

题意:

给你T天股票的信息,每天的信息分别可以表示为:APi , BPi , ASi , BSi,分别表示第

i天时股票的买入单价、卖出单价、第i天最多可以买入的股票数、最多可以卖出的股票

数,并且还有一个约束条件,那就是两个交易日相距的天数要大于W天,问最终最多能

获利多少。

思路:

这题的dp方程很容易列,用dp[i][j] 表示第i天持有j股的最大拥有的钱数,转移方程为:

不买不卖:

dp[i][j] = MAX( dp[i][j]  , dp[i-1][j] ) ;

买入:

dp[i][j] = MAX(  dp[ pre ][ k] - (j - k) * AP[i]  );

则:dp[i][j] + j  * AP[i]  = MAX( dp[pre][k] + k *AP[i] );

令FF(k) = dp[ pre ][k] + k * AP[i] ;

则:dp[i][j] = MAX( FF(k) ) - j *AP[i] ; //这样就转化为经典的单调dp了,我们只需要用一个

单调队列来存放k的决策点就可以了。

卖出:

dp[i][j] = MAX( dp[pre][k] + (k - j )*BP[i] );

则:dp[i][j] + j  * AP[i]  = MAX( dp[pre][k] + k *BP[i] );

令FFF(k) = dp[ pre ][k] + k * BP[i] ;

则:dp[i][j] = MAX( FFF(k) ) - j *AP[i] ;  //情况和上面的类似。

这样每次求dp[i][j] 的时候,我们可以得出以下的一个重要结论:在第i天的时候,最优的pre

应该是:pre = i - W 。其实证明这个结论很简单, 假设pre1 < pre2,我们假设dp[pre1][k] >

dp[pre2][k] ,但是这种情况是不可能出现的,因为我们可以这样考虑,我现在从pre1天到

pre2天这中间都不交易,那么我就可以得到dp[pre2][k] >= dp[pre1][k],所以说上面的假设是

不可能成立的,因此我们每次只要将pre赋值为i - W就可以了(一开始我还在这里加了一个

单调队列维护,呃。。。)。接着就是每次求dp[i][j] 的时候分别用两个队列分别维护买入和

卖出就可以了。

代码:

#include<stdio.h>
#include<string.h>
#define MAX(a,b) (a)>(b)?(a):(b)
const int inf = (1<<30) ;
int TT ;
const int MAXN = 2010 ;
int T , MAXP , W ;
int AP[MAXN] , BP[MAXN] , AS[MAXN] , BS[MAXN] ;
int dp[MAXN][MAXN] ;
int que1[MAXN]  ,que2[MAXN] ;
int f1, r1 , f2,  r2 ;

void DP(){
    for(int i=0;i<=T;i++){
        for(int j=0;j<=MAXP;j++){
            dp[i][j] = -inf ;
        }
    }
    dp[0][0] = 0 ;
    int pre = 0 ;
    for(int i=1;i<=T;i++){
        //不买不卖
        for(int j=0;j<=MAXP;j++){
            dp[i][j] = MAX( dp[i][j] , dp[i-1][j] );
        }
        //买
        f1 = r1 = 0 ;
        que1[r1++] = 0 ;
        for(int j=1;j<=MAXP;j++){
            while( f1 < r1 ){
                if( j-que1[f1] <= AS[i] )   break ;
                else        f1++ ;
            }
            if(f1 == r1)    break ;
            int a = que1[f1] ;
            dp[i][j] = MAX( dp[i][j] , dp[pre][a] - (j-a)*AP[i] );
            int aa = dp[pre][j] + j * AP[i] ;

            while(f1 < r1){
                int b = que1[r1-1] ;
                int bb = dp[pre][b] + b * AP[i] ;
                if(aa > bb) r1-- ;
                else        break ;
            }
            que1[r1++] = j ;
        }
         //卖
        f2 = r2 = 0 ;
        que2[r2++] = MAXP ;
        for(int j=MAXP-1;j>=0;j--){
            while(f2 < r2){
                if( que2[f2]-j <= BS[i] )   break ;
                else        f2++;
            }
            if(f2 == r2)    break ;
            int b = que2[f2] ;
            dp[i][j] = MAX( dp[i][j] , dp[pre][b] + (b-j)*BP[i] );
            int aa = dp[pre][j]+j*BP[i] ;
            while(f2 < r2){
                int b = que2[r2-1] ;
                int bb = dp[pre][b] + b*BP[i] ;
                if(aa > bb) r2-- ;
                else        break ;
            }
            que2[r2++] = j;
        }
        if( pre < i - W ){
            pre = i - W ;
        }
    }
    int _max = -inf ;
    for(int j=0;j<=MAXP;j++){
        _max = MAX( _max, dp[T][j] );
    }
    printf("%d\n",_max);
}

int main(){
    scanf("%d",&TT);
    for(int cas=1;cas<=TT;cas++){
        scanf("%d%d%d",&T,&MAXP,&W);
        for(int i=1;i<=T;i++)
            scanf("%d%d%d%d",&AP[i],&BP[i],&AS[i],&BS[i]);
        DP();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值