题意:给出一种股票n天以来每天的购入价ap和卖出价bp,一开始有无限本金,0张股票,问最多能赚多少钱。
限制:每天只能进行买、卖、什么都不做这三个操作中的一个,每天最多买as张股票,最多卖bs张股票,并且任意一天手里的股票总张数不能超过m张,买卖操作间隔天数必须严格大于w天(比如第10天买了股票,下一次买卖操作必须在1+w+1天或者之后)
思路:dp【i】【j】表示第i天手里的股票数为j时的最大收入,容易写出状态转移方程:
什么都不做:dp[i][j] = max{dp[i-1][j]};
买:dp[i][j] = max{dp[i-w-1][k]-(j-k)*ap[i]} = max{dp[i-w-1][k]+k*ap[i]}-j*ap[i];
(max(0,j-as[i])<= k<=j);
卖:dp[i][j] = max{dp[i-w-1][k]+(k-j)*bp[i]} = max{dp[i-w-1][k]+k*bp[i]}-j*bp[i];
(j<=k<=min(m, k+bs[i]));
有了上面的式子,就很容易用单调队列优化了。对于买操作,我们可以想象一个大小为as[i]+1的窗口在dp[i-w+1][]上从左向右滑动;对于卖操作,我们可以想象一个大小为bs[i]+1的窗口在dp[i-w+1][]上从右向左滑动
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 2010; const int INF = 0x3f3f3f3f; int T, n, m, w, ap[maxn], bp[maxn], as[maxn], bs[maxn], dp[maxn][maxn]; struct Queue { int l, r; int p[maxn], q[maxn]; void init() { l = 1, r = 1; } void push(int x, int pos) { while (l < r && x > q[r-1]) r--; r++; p[r-1] = pos; q[r-1] = x; } void pop(int x, int flag) { if (flag == 1) while (l < r && p[l] < x) l++; else if (flag == 2) while (l < r && p[l] > x) l++; } int front() { return q[l]; } }q1, q2; int main() { scanf("%d", &T); while (T--) { scanf("%d%d%d", &n, &m, &w); for (int i = 1; i <= n; i++) scanf("%d%d%d%d", &ap[i], &bp[i], &as[i], &bs[i]); for (int i = 0; i <= n; i++) for (int j = 0; j <= m; j++) dp[i][j] = -INF; dp[0][0] = 0; for (int i = 1; i <= w+1; i++) { for (int j = 0; j <= as[i]; j++) { dp[i][j] = -ap[i]*j; } } for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) dp[i][j] = max(dp[i][j], dp[i-1][j]); if (i <= w+1) continue; q1.init(); q2.init(); for (int j = 0; j <= m; j++) { q1.push(dp[i-w-1][j]+j*ap[i], j); q1.pop(j-as[i], 1); dp[i][j] = max(dp[i][j], q1.front()-j*ap[i]); } for (int j = m; j >= 0; j--) { q2.push(dp[i-w-1][j]+j*bp[i], j); q2.pop(j+bs[i], 2); dp[i][j] = max(dp[i][j], q2.front()-j*bp[i]); } } int ans = -INF; for (int i= 0; i <= m; i++) ans = max(ans, dp[n][i]); printf("%d\n", ans); } return 0; }