【SCOI 2010】股票交易
題目
最近 \(\text{lxhgww}\) 又迷上了投資股票,通過一段時間的觀察和學習,他總結出了股票行情的一些規律。
通過一段時間的觀察,\(\text{lxhgww}\) 預測到了未來 \(T\) 天內某只股票的走勢,第 \(i\) 天的股票買入價為每股 \(ap_i\),第 \(i\) 天的股票賣出價為每股 \(bp_i\)(數據保證對於每個 \(i\),都有 \(ap_i \geq bp_i\)),但是每天不能無限制地交易,於是股票交易所規定第 \(i\) 天的一次買入至多只能購買 \(as_i\) 股,一次賣出至多只能賣出 \(bs_i\) 股。
另外,股票交易所還制定了兩個規定。為了避免大家瘋狂交易,股票交易所規定在兩次交易(某一天的買入或者賣出均算是一次交易)之間,至少要間隔 \(w\)
在第 \(1\) 天之前,\(\text{lxhgww}\) 手裏有一大筆錢(可以認為錢的數目無限),但是沒有任何股票,當然,\(T\) 天以後,\(\text{lxhgww}\) 想要賺到最多的錢,聰明的程序員們,你們能幫助他嗎?
分析
設 \(f(i,j)\) 代表第 \(i\) 持有 \(j\) 股的最大收益。
\[
f(i,j) = \max\left\{ \begin{array}{}
\max_{k = 1}^{as_i} f(i - w - 1,j - k) - ap_i\times k \\max_{k = 1}^{bs_i} f(i - w - 1,j + k) + bp_i\times k \f(i - 1,j)
\end{array} \right.
\]
很容易得出,該方程的時間復雜度為 \(\Theta (n^3)\)。顯然會\(\rm T\)。
以只買入的 \(\max_{k = 1}^{as_i} f(i - w - 1,j - k) - ap_i\times k\) 看,我們發現我們要查詢的 \(f\) 總是一段連續的區間,但是若使用單調隊列維護 \(f\) ,\(ap_i\times k\) 又很惡心。
但是我們通過觀察,對於每個 \(ap_i\times k\) 和 \(bp_i \times k\) 所影響到的要查詢的 \(f\) ,若 \(j + 1\) ,則它們都會同時加上 \(ap_i\) 或 \(-bp_i\)。
比如原先的 \(f(i - w - 1, j) - ap_i,\ f(i - w - 1, j - 1) - 2ap_i,\cdots,f(i - w - 1, j + k) - ap_i\times k\)
當區間右移一次時,它們的影響變成了:$f(i - w - 1, j - 1) - ap_i,?f(i - w - 1, j - 2) - 2ap_i,\cdots $
所以,當要查詢的區間右移一次時,它們的相對大小關系是不會改變的,我們可以對原 \(\rm dp\) 式進行變換來更好的運用這個性質。
以買入為例,\(\max_{k = 1}^{as_i} f(i - w - 1,j - k) - ap_i\times k\),若令 \(p = j - k\),則變為:
\[
\begin{array}{}
& \max_{p = j - 1}^{j - as_i} f(i - w - 1,p) - ap_i\times (j - p) \\
= & \max_{p = j - 1}^{j - as_i} ( f(i - w - 1,p) + ap_i\times p ) - ap_i\times j
\end{array}
\]
這麽一來,\(f\) 與 \(ap_i\) 就有了一一對應的關系,可以很容易的利用單調隊列優化。
同理,賣出也可以這樣處理,變式為 \(\max_{p = j + 1}^{j + bs_i} (f(i - w - 1,p) + bp_i\times p) - bp_i\times j\)。
接下來就是常規的單調隊列優化,時間復雜度為 \(\Theta (n ^ 2)\)。
代碼
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000 + 5, inf = 0x3f3f3f3f;
int f[MAXN][MAXN], ap[MAXN], bp[MAXN], as[MAXN], bs[MAXN], w, t, maxp;
struct monQue {
pair<int, int> data[MAXN];
int head, tail;
void clear() {head = 0, tail = 0;}
bool empty() {return tail <= head;}
void push(pair<int, int> x) {
while(!empty() && data[tail - 1] <= x) tail--;
data[tail++] = x;
}
void pop() {head++;}
pair<int, int> front() {return data[head];}
} sell, buy;
int main() {
memset(f, ~0x3f, sizeof(f));
scanf("%d%d%d", &t, &maxp, &w);
for(int i = 1; i <= t; i++)
scanf("%d%d%d%d", &ap[i], &bp[i], &as[i], &bs[i]);
for(int i = 1; i <= t; i++) {
buy.clear(); sell.clear();
for(int j = 0; j <= maxp; j++) {
f[i][j] = max(f[i - 1][j], f[i][j]);
if(j <= as[i]) f[i][j] = max(-ap[i] * j, f[i][j]);
if(i - w - 1 <= 0) continue;
if(j) {
while(!buy.empty() && buy.front().second < j - as[i]) buy.pop();
buy.push(make_pair(f[i - w - 1][j - 1] + ap[i] * (j - 1), j - 1));
f[i][j] = max(buy.front().first - ap[i] * j, f[i][j]);
}
}
if(i - w - 1 <= 0) continue;
for(int j = maxp - 1; j >= 0; j--) {
while(!sell.empty() && sell.front().second > j + bs[i]) sell.pop();
sell.push(make_pair(f[i - w - 1][j + 1] + bp[i] * (j + 1), j + 1));
f[i][j] = max(sell.front().first - bp[i] * j, f[i][j]);
}
}
int ans = -inf;
for(int i = 0; i <= maxp; i++) {
ans = max(f[t][i], ans);
}
printf("%d\n", ans);
return 0;
}
後記
終於把這個坑填上了。。
【SCOI 2010】股票交易