单调队列优化DP Hdu3401-Trade

题意

在第i天,股票买入价格api,卖出价格bpi (bpi ≤ api);最多买入asi份股票,卖出bsi份股票。两个交易日之间的间隔需超过w天。最多持有MaxP份股票。求最多可以攒多少钱?

分析

设 dp[i][j] 表示第i天持有j份股票时的最多赚钱数。
到达这个状态有三种情况,取其中最大值。

  1. 没有买卖,dp[i-1][j] → dp[i][j]
  2. 买入k个股票,dp[i-w-1][j-k] - k*api → dp[i][j]
  3. 卖出k个股票,dp[i-w-1][j+k] + k*bpi → dp[i][j]

此时更新该状态的时间复杂度为O(asi+bsi),而总共有T * MaxP个状态,故总的时间复杂度为O(T * MaxP * asi) = O(8 * 109),显然会TLE。所以需要优化,这时就用到了单调队列优化。

以买入股票分析,更新状态[i][j]需要用到状态[i-w-1][j-asi] ~ [i-w-1][j-1]。因此我们在单调队列中动态维护[j-asi ~ j-1]范围内的状态值。维护这个单调队列包括两个动作。

  1. 删除队列首部。当队列首部元素失效的时候,我们将这个元素弹出。
  2. 从队列尾部加入新元素。当需要加入一个新元素的时候,先将队列尾部更新效率比当前新元素低的无用元素弹出。然后再加入新元素。

这时这个单调队列中的元素,从首部到尾部,更新效率单调下降,时效性单调上升。而且所有的元素都只会被删除一次,所以更新第i天的所有状态值的复杂度为O(MaxP)。所以总共的时间复杂度为O(T * MaxP)。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAX_T = 2e3;
const int MAX_P = 2e3;
const int INF_ = 0x3f3f3f3f;
int dp[MAX_T+1][MAX_P+1];
// 单调队列, 记录持有股票数
int ID[MAX_T+1];
int front, rear;
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int T, MaxP, W;
        int api, bpi, asi, bsi;
        scanf("%d%d%d", &T, &MaxP, &W);
        for (int i = 0; i < T; i++)
        {
            scanf("%d%d%d%d", &api, &bpi, &asi, &bsi);
            // 初次买入
            for (int j = 0; j <= asi; j++) dp[i][j] = -j*api;
            for (int j = asi+1; j <= MaxP; j++) dp[i][j] = -INF_;
            // 无交易
            if (i > 0)
            {
                for (int j = 0; j <= MaxP; j++)
                    dp[i][j] = max(dp[i][j], dp[i-1][j]);
            }
            if (i - W - 1 < 0) continue;
            // 买入
            // 单调队列, [front, rear]
            int pre = i-W-1;
            front = 0;
            rear = -1;
            // 初始化单调队列
            for (int j = MaxP; j >= MaxP - asi; j--)
            {
                // 到达ID[rear]有更小的开销
                while (front <= rear && dp[pre][j]-(ID[rear]-j)*api >= dp[pre][ID[rear]]) 
                    rear--;
                ID[++rear]=j;
            }
            for (int j = MaxP; j >= 0; j--)
            {
                dp[i][j] = max(dp[i][j], dp[pre][ID[front]]-(j-ID[front])*api);
                // 删除队列首部
                if (ID[front] == j) front++;
                int jj=j-asi-1;
                if (jj < 0) continue;
                // 删除无用元素
                while (front <= rear && dp[pre][jj]-(ID[rear]-jj)*api >= dp[pre][ID[rear]]) 
                    rear--;
                // 加入新元素
                ID[++rear]=jj;
            }
            // 卖出
            front = 0;
            rear = -1;
            for (int j = 0; j <= bsi; j++)
            {
                while (front <= rear && dp[pre][j]+(j-ID[rear])*bpi >= dp[pre][ID[rear]]) 
                    rear--;
                ID[++rear]=j;
            }
            for (int j = 0; j <= MaxP; j++)
            {
                dp[i][j] = max(dp[i][j], dp[pre][ID[front]]+(ID[front]-j)*bpi);
                if (j == ID[front]) front++;
                int jj = j+bsi+1;
                if (jj > MaxP) continue;
                while (front <= rear && dp[pre][jj]+(jj-ID[rear])*bpi >= dp[pre][ID[rear]])
                    rear--;
                ID[++rear] = jj;
            }
        }
        int res = dp[T-1][0];
        printf("%d\n", res);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值