题意
在第i天,股票买入价格api,卖出价格bpi (bpi ≤ api);最多买入asi份股票,卖出bsi份股票。两个交易日之间的间隔需超过w天。最多持有MaxP份股票。求最多可以攒多少钱?
分析
设 dp[i][j] 表示第i天持有j份股票时的最多赚钱数。
到达这个状态有三种情况,取其中最大值。
- 没有买卖,dp[i-1][j] → dp[i][j]
- 买入k个股票,dp[i-w-1][j-k] - k*api → dp[i][j]
- 卖出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]范围内的状态值。维护这个单调队列包括两个动作。
- 删除队列首部。当队列首部元素失效的时候,我们将这个元素弹出。
- 从队列尾部加入新元素。当需要加入一个新元素的时候,先将队列尾部更新效率比当前新元素低的无用元素弹出。然后再加入新元素。
这时这个单调队列中的元素,从首部到尾部,更新效率单调下降,时效性单调上升。而且所有的元素都只会被删除一次,所以更新第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);
}
}