洛谷P2569 [SCOI2010]股票交易
题目描述
最近 \(\text{lxhgww}\) 又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。
通过一段时间的观察,\(\text{lxhgww}\) 预测到了未来 \(T\) 天内某只股票的走势,第 \(i\) 天的股票买入价为每股 \(AP_i\),第 \(i\) 天的股票卖出价为每股 \(BP_i\)(数据保证对于每个 \(i\),都有 \(iAP_i \geq BP_i\)),但是每天不能无限制地交易,于是股票交易所规定第 \(i\) 天的一次买入至多只能购买 \(AS_i\) 股,一次卖出至多只能卖出 \(BS_i\) 股。
另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔 \(W\) 天,也就是说如果在第 \(i\) 天发生了交易,那么从第 \(i+1\) 天到第 \(i+W\) 天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过 \(\text{MaxP}\)。
在第 \(1\) 天之前,\(\text{lxhgww}\) 手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,\(T\) 天以后,\(\text{lxhgww}\) 想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?
输入格式
输入数据第一行包括 \(3\) 个整数,分别是 \(T\),\(\text{MaxP}\),\(W\)。
接下来 \(T\) 行,第 \(i\) 行代表第 \(i-1\) 天的股票走势,每行 \(4\) 个整数,分别表示 \(AP_i,\ BP_i,\ AS_i,\ BS_i\)。
输出格式
输出数据为一行,包括 \(1\) 个数字,表示 \(\text{lxhgww}\) 能赚到的最多的钱数。
输入输出样例
输入 #1
5 2 0
2 1 1 1
2 1 1 1
3 2 1 1
4 3 1 1
5 4 1 1
输出 #1
3
说明/提示
对于 \(30\%\) 的数据,\(0\leq W<T\leq 50,1\leq\text{MaxP}\leq50\)
对于 50%50%50% 的数据,\(0\leq W<T\leq 2000,1\leq\text{MaxP}\leq50\)
对于 100%100%100% 的数据,\(0\leq W<T\leq 2000,1\leq\text{MaxP}\leq2000\)
对于所有的数据,\(1\leq BP_i\leq AP_i\leq 1000,1\leq AS_i,BS_i\leq\text{MaxP}\)
Solution
状态:
\(f_{i,j}\) 表示在第 \(i\) 天持有 \(j\) 张股票时可以赚到的最多钱数。
转移:
可以分为4种情况
-
买 \(j\) 张股票
\(f_{i,j}=-1 \times ap_i \times j\) -
不买也不卖
直接从 \(i-1\) 天转移
\(f_{i,j}=\max(f_{i,j},f_{i-1,j})\) -
假设已经有 \(k\) 张股票,要买 \(j-k\) 张
因为2次交易之间要隔 \(w\) 天,所以从 \(i-w-1\) 天转移
\(f_{i,j}=\max(f_{i,j},f_{i-w-1,k}-(j-k) \times ap_i)\) -
假设已经有 \(k\) 张股票,要卖 \(k-j\) 张
一样是从 \(i-w-1\) 天转移
\(f_{i,j}=\max(f_{i,j},f_{i-w-1,k}+(k-j) \times bp_i)\)
但这样做是 \(O(N^3)\) 的,因此要想办法去掉一层循环。
以第3种为例
\(f_{i,j}=\max(f_{i,j},f_{i-w-1,k}-(j-k) \times ap_i)\)
其中 \(f_{i-w-1,k}-(j-k) \times ap_i=f_{i-w-1,k}+k \times ap_i-j \times ap_i\)
因此 \(f_{i,j}=\max(f_{i,j},f_{i-w-1,k}+k \times ap_i)-j \times ap_i \qquad (j-as_i \leq k < j)\)
所以就可以用单调队列优化了,只需要维护 \(j-as_i \leq k < j\) 中 \(f_{i-w-1,k}+k \times ap_i\) 的最大值。
第4种同理
\(f_{i,j}=\max(f_{i,j},f_{i-w-1,k}+k \times bp_i)-j \times bp_i \qquad (j<k \leq j+bs_i)\)
维护 \(j<k \leq j+bs_i\) 中 \(f_{i-w-1,k}+k \times bp_i\) 的最大值
但这里要从大到小排序,因为要卖股票 \(k>j\) 是用大的推小的。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=2010;
int n,m,w;
int f[N][N];
int q[N],h,t;
int main()
{
scanf("%d%d%d",&n,&m,&w);
memset(f,128,sizeof(f));
for(int i=1;i<=n;i++)
{
int ap,bp,as,bs;
scanf("%d%d%d%d",&ap,&bp,&as,&bs);
for(int j=0;j<=as;j++)
f[i][j]=-1*ap*j;
for(int j=0;j<=m;j++)
f[i][j]=max(f[i][j],f[i-1][j]);
if(i<=w) continue;
h=1,t=0;
for(int j=0;j<=m;j++)
{
while(h<=t&&q[h]<j-as) h++;
while(h<=t&&f[i-w-1][j]+ap*j>=f[i-w-1][q[t]]+ap*q[t]) t--;
q[++t]=j;
f[i][j]=max(f[i][j],f[i-w-1][q[h]]+ap*q[h]-ap*j);
}
h=1,t=0;
for(int j=m;j>=0;j--)
{
while(h<=t&&q[h]>j+bs) h++;
while(h<=t&&f[i-w-1][j]+bp*j>=f[i-w-1][q[t]]+bp*q[t]) t--;
q[++t]=j;
f[i][j]=max(f[i][j],f[i-w-1][q[h]]+bp*q[h]-bp*j);
}
}
int ans=0;
for(int i=0;i<=m;i++)
ans=max(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}