1.14单调队列优化dp练习
问题描述
我们需要帮助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;
}