1.14单调队列优化dp练习

题目P2569 [SCOI2010] 股票交易

问题描述
我们需要帮助lxhgww制定一个交易策略,使得在 T 天内赚到最多的钱。每天的交易有以下限制:

  • 第 i天的买入价为 AP_i,卖出价为 BP_i,且AP_i≥BP_i
  • 第 i 天的买入上限为 AS_i,卖出上限为 BS_i
  • 两次交易之间至少要间隔 W 天
  • 任何时候持有的股票数不能超过 MaxP

状态转移方程

  • 状态定义:dp[i][j],表示在第 i 天结束时持有 j 股股票时的最大收益
  • 讨论:某一天,要么买股,要么卖股,要么不买也不卖。而买股又可分为两个情况,要么凭空买,跟前面交不交易没有任何关系,要么在前面交易的基础上买股,通俗一点说,也就是该状态是否需要由前面的状态转移而来
    因此,一共有四种情况,其中只有一个情况并不需要其他状态转移,单独讨论:
    1.凭空买
    直接在第 i 天买入 j 张股票,不依赖于前一个状态。转移方程:
    dp[i][j]=−AP_i×j (0≤j≤AS_i)
    ​2.不买也不卖
    直接从第 i−1 天的状态转移过来,持有股票数量不变
    转移方程:
    dp[i][j]=max(dp[i][j],dp[i−1][j])
    3.在第 i−W−1 天的基础上买入
    从第 i−W−1 天的状态转移过来,买入 j−k 张股票
    转移方程:
    dp[i][j]=max(dp[i][j],dp[i−W−1][k]−(j−k)×AP_i) (j−AS_i≤k<j)
    4.在第 i−W−1 天的基础上卖出
    从第i−W−1 天的状态转移过来,卖出 k−j 张股票
    转移方程:
    dp[i][j]=max(dp[i][j],dp[i−W−1][k]+(k−j)×BP_i) (j<k≤j+BS_i)

单调性优化

  • 上面的 3,4 两种情况,时间复杂度是三次方级的 ,时间复杂度降到二次方级
  • 为了降低时间复杂度,我们可以使用单调性优化。滑动窗口.我们可以使用单调队列来优化第 3 和第 4 种情况的转移方程
    1.在之前的基础上买股票
    转移公式中与 k 相关的项是递减关系,可从小到大遍历 j
    优化后的转移方程:
    dp[i][j]=max(dp[i][j],max(dp[i−W−1][k]+k×AP_i)−j×AP_i) (j−AS_i≤k<j)
    使用单调队列维护
    max(dp[i−W−1][k]+k×AP_i)
    2.在之前的基础上卖股票
    转移公式中与 k 相关的项是递增关系,可从大到小遍历 j
    优化后的转移方程:
    dp[i][j]=max(dp[i][j],max(dp[i−W−1][k]+k×BP_i)−j×BP_i) (j<k≤j+BS_i)
    ​使用单调队列维护
    max(dp[i−W−1][k]+k×BP_i)

边界处理

  • 如果 i≤W,则无法从第 i−W−1 天转移
  • 最后一天的 dp[T][j](包括持有股票的情况)都需要考虑,答案取其中的最大值

代码

#include <bits/stdc++.h>
using namespace std;

const int INF = 1 << 30;
const int MAX_T = 2000;
const int MAX_M = 2000;

int T, MaxP, W;
int dp[MAX_T + 1][MAX_M + 1]; // 动态规划数组
int AP, BP, AS, BS;

int main() {
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>T>>MaxP>>W;
    // 初始化 DP 数组为 -INF
    memset(dp, -128, sizeof(dp));
    // 遍历每一天
    for (int i = 1; i <= T; i++) {
        cin>>AP>>BP>>AS>>BS;
        // 初始购买,直接计算
        for (int j = 0; j <= AS; j++) {
            dp[i][j] = max(dp[i][j], -j * AP);
        }
        // 不买不卖,继承前一天的状态
        for (int j = 0; j <= MaxP; j++) {
            dp[i][j] = max(dp[i][j], dp[i - 1][j]);
        }
        if (i > W) {
            // 买股票:单调队列优化
            int l = 1, r = 0, q[MAX_M + 1];
            for (int j = 0; j <= MaxP; j++) {
                while (l <= r && q[l] < j - AS) l++; // 弹出过期元素
                while (l <= r && dp[i - W - 1][q[r]] + q[r] * AP <= dp[i - W - 1][j] + j * AP) r--; // 保持单调性
                q[++r] = j; // 插入新元素
                if (l <= r) {
                    dp[i][j] = max(dp[i][j], dp[i - W - 1][q[l]] + q[l] * AP - j * AP);
                }
            }
            // 卖股票:单调队列优化
            l = 1, r = 0;
            for (int j = MaxP; j >= 0; j--) {
                while (l <= r && q[l] > j + BS) l++; // 弹出过期元素
                while (l <= r && dp[i - W - 1][q[r]] + q[r] * BP <= dp[i - W - 1][j] + j * BP) r--; // 保持单调性
                q[++r] = j; // 插入新元素
                if (l <= r) {
                    dp[i][j] = max(dp[i][j], dp[i - W - 1][q[l]] + q[l] * BP - j * BP);
                }
            }
        }
    }
    int ans = 0;
    for (int j = 0; j <= MaxP; j++) {
        ans = max(ans, dp[T][j]);
    }
    cout<<ans<<'\n';
    return 0;
}
posted @ 2025-01-14 08:36  fufuaifufu  阅读(18)  评论(0编辑  收藏  举报