【题解】P2569 [SCOI2010]股票交易 - 优化 $dp$ - 单调队列
P2569 [SCOI2010]股票交易
声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。
题目描述
一只股票在第 \(i\) 天时,每股买入为 \(AP_i\) 元,卖出为 \(BP_i\) 元,最多买入 \(AS_i\) 股,最多卖出 \(BS_i\) 股
第 \(0\) 天时有 \(0\) 只股票,无限多的钱;任何时候手上拥有的股票数不能超过 \(MaxP\) 股,在第 \(i\) 天进行一次交易后(买入 / 卖出)\(i + 1\) 天 - \(i + W\) 天内都不能再进行交易
求第 \(T\) 天时能赚的最多钱
\(\\\)
\(\\\)
\(Solution\)
一道非常经典的考验思维又考验优化又考验代码细节实现能力但又不毒瘤不算特别难的好 \(dp\) 题!!!特此写篇题解记录一下!!!
\(\\\)
\(1st \ step\) 基础思考
通过“第 \(i\) 天、有 \(j\) 股票”等经典字眼,以及暂时无法发现和这道题有关的特殊性质,我们可以敏锐地发觉这是一道以 \(dp[i][j]\) 为基础状态,意义为“第 \(i\) 天交易完后,手里还有 \(j\) 股股票的最大值”的 \(dp\) 题
同时可以得到基础转移方程(任何边界都还未设定)
\(\\\)
\(2nd \ step\) 优化
基础转移方程的时间复杂度是 \(O(n^4)\) 的,同时感觉到有很多冗余的转移,于是进行优化
优化之前有个小技巧就是,先看看数据范围,大致确定要优化到什么程度;对于这道题,貌似要卡到 \(O(n^2)\) 或 \(O(n^2 \log n)\) 的
优化的时候一般分两部分,分别在状态和转移上进行优化
先看状态
通过简单分析会发现,对于天数和股票数,缺了一样就是无法转移的,所以在状态上已经无法优化了
再看转移
之前写基础转移时,对于天数和股票数我们都进行了枚举,于是分别从这两方面寻找突破口
(每个优化掉一个 \(n\) 不就正好吗!冲鸭!)
对于天数,是否可以省略掉枚举,直接从固定的某一天转移?
答案是肯定的:对于第 \(i\) 天,直接由第 \(i - W - 1\) 天转移来。
为什么可以这样?万一之前的更优呢?通过把基础转移方程稍微写详细一点,我们会在当 \(s = j\) 的情况中发现,这个状态是可以延续前一个状态的;若加上一句 \(dp[i][j] = max(dp[i][j], dp[i][j - 1])\) ,这样就可以把前面所有状态的最优解全部转移到第 \(i\) 天来,所以对于第 \(i - W - 1\) 天的状态,已经是处理过的状态的最优解的合并了,即可直接转移
(此时复杂度已经降到了 \(O(n^3)\) 了!!!胜利在望!!!)
对于股票数,我们可以再把基础转移方程写详细一点
拿 \(s > j\) 做例子
又由于 \(s\) 的范围为 \([j, j + BS_i]\),于是可以发现这是一个滑动窗口(不会请问度娘)
这就把总复杂度降为 \(O(n^2 )\) 了
\(\\\)
\(3rd \ step\) 细节处理
细节往往是 \(dp\) 中非常重要的一环,在某些毒瘤题中甚至比方程还麻烦...
一般来说细节处理主要分为边界、初始化、代码实现三大块
对于这道题,有以下几点值得注意的:
\(1.\) 要把所有状态初始为 \(-inf\)
\(2.\) 对于每天的状态,要添加一句 F(j, 0, as) dp[i][j] = -j * as
作为初始状态(其实也可以看作一种情况)
\(3.\) 由于单调队列进行转移时要求 \(i - W - 1>= 0\) ,所以 \(i <= W\) 的直接跳过,否则下标会错
\(4.\) 单调队列实现时注意细节
\(5.\) 最后输出答案不用在 \(dp[T][0 - Maxp]\) 中找最大的了,\(dp[T][0]\) 一定就是最优的
\(\\\)
完结撒花✿✿ヽ(°▽°)ノ✿
\(\\\)
\(\\\)
\(Code\)
#include<bits/stdc++.h>
#define ll long long
#define F(i, x, y) for(int i = x; i <= y; ++ i)
using namespace std;
int read();
const int N = 2e3 + 5;
int T, Maxp, W;
int ap, bp, as, bs;
int head, tail, q[N << 1];
ll dp[N][N], ans;
int main()
{
T = read(), Maxp = read(), W = read();
memset(dp, -0x3f, sizeof(dp));
F(i, 1, T)
{
ap = read(), bp = read(), as = read(), bs = read();
F(j, 0, as) dp[i][j] = - j * ap;
F(j, 0, Maxp) dp[i][j] = max(dp[i][j], dp[i - 1][j]);
if(i <= W || i == 1) continue;
head = 1, tail = 0;
F(j, 0, Maxp)
{
while(head <= tail && q[head] < j - as) ++ head;
while(head <= tail && dp[i - W - 1][q[tail]] + q[tail] * ap <= dp[i - W - 1][j] + j * ap) -- tail;
q[++ tail] = j, dp[i][j] = max(dp[i][j], dp[i - W - 1][q[head]] - (j - q[head]) * ap);
}
head = 1, tail = 0;
for(int j = Maxp; j >= 0; -- j)
{
while(head <= tail && q[head] > j + bs) ++ head;
while(head <= tail && dp[i - W - 1][q[tail]] + q[tail] * bp <= dp[i - W - 1][j] + j * bp) -- tail;
q[++ tail] = j, dp[i][j] = max(dp[i][j], dp[i - W - 1][q[head]] + (q[head] - j) * bp);
}
}
printf("%lld\n", dp[T][0]);
return 0;
}
int read()
{
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
带输出调试语句的:
#include<bits/stdc++.h>
#define ll long long
#define F(i, x, y) for(int i = x; i <= y; ++ i)
using namespace std;
int read();
const int N = 2e3 + 5;
int T, Maxp, W;
int ap, bp, as, bs;
int head, tail, q[N << 1];
ll dp[N][N], ans;
int main()
{
T = read(), Maxp = read(), W = read();
memset(dp, -0x3f, sizeof(dp));
F(i, 1, T)
{
//printf("Day:%d\n", i);
ap = read(), bp = read(), as = read(), bs = read();
F(j, 0, as) dp[i][j] = - j * ap;
F(j, 0, Maxp) dp[i][j] = max(dp[i][j], dp[i - 1][j]);
if(i <= W || i == 1) continue;
head = 1, tail = 0;
//puts("buy:");
F(j, 0, Maxp)
{
while(head <= tail && q[head] < j - as) ++ head;
while(head <= tail && dp[i - W - 1][q[tail]] + q[tail] * ap <= dp[i - W - 1][j] + j * ap) -- tail;
q[++ tail] = j, dp[i][j] = max(dp[i][j], dp[i - W - 1][q[head]] - (j - q[head]) * ap);//, printf("j:%d j':%d\n", j, q[head]);
}
//printf("dp:"); F(j, 0, Maxp) printf("%lld ", dp[i][j]); puts("");
head = 1, tail = 0;
//puts("sell:");
for(int j = Maxp; j >= 0; -- j)
{
while(head <= tail && q[head] > j + bs) ++ head;
while(head <= tail && dp[i - W - 1][q[tail]] + q[tail] * bp <= dp[i - W - 1][j] + j * bp) -- tail;
q[++ tail] = j, dp[i][j] = max(dp[i][j], dp[i - W - 1][q[head]] + (q[head] - j) * bp);//, printf("j:%d j':%d\n", j, q[head]);
}
//printf("dp:"); F(j, 0, Maxp) printf("%lld ", dp[i][j]); puts("");
}
printf("%lld\n", dp[T][0]);
return 0;
}
int read()
{
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}