[周末训练]股票交易
题目
【内存限制:$512MiB$】【时间限制:$1000ms$】
【标准输入输出】【题目类型:传统】【评测方式:文本比较】
【题目描述】
原题来自:SCOI 2010
最近 $lxhgww$ 又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。
通过一段时间的观察,$lxhgww$ 预测到了未来 $T$ 天内某只股票的走势,第 $i$ 天的股票买入价为每股 $AP_i$,第 $i$ 天的股票卖出价为每股 $BP_i$(数据保证对于每个 $i$,都有 $AP_i\ge BP_i$),但是每天不能无限制地交易,于是股票交易所规定第 $i$ 天的一次买入至多只能购买 $AS_i$ 股,一次卖出至多只能卖出 $BS_i$ 股。
另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔 $W$ 天
也就是说如果在第 $i$ 天发生了交易,那么从第 $i+1$ 天到第 $i+W$ 天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过 $\text{MaxP}$。
在第一天之前,$lxhgww$ 手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,$T$ 天以后,$lxhgww$ 想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?
【输入格式】
输入数据第一行包括三个整数,分别是 $T,\text{MaxP},W$。
接下来 $T$ 行,第 $i$ 行代表第 $i-1$ 天的股票走势,每行四个整数,分别表示 $AP_i,BP_i,AS_i,BS_i$。
【输出格式】
输出数据为一行,包括一个数字,表示 $lxhgww$ 能赚到的最多的钱数。
【样例】
样例输入
5 2 0 2 1 1 1 2 1 1 1 3 2 1 1 4 3 1 1 5 4 1 1
样例输出
3
【数据范围与提示】
对于 $30\%$ 的数据,$0\le W\lt T\le 50,1\le \text{MaxP}\le 50$;
对于 $50\%$ 的数据,$0\le W\lt T\le 2000,1\le \text{MaxP}\le 50$;
对于 $100\%$ 的数据,$0\le W\lt T\le 2000,1\le \text{MaxP}\le 2000,1\le BP_i\le AP_i\le 1000,1\le AS_i,BS_i\le \text{MaxP}$。
题解
【做题经历】
看到输入的时候我被吓到了......
因为对于我这种蒟蒻来说,就只是这道题的输入的定义,我就可以被绕晕。
但是仔细思考了一下,感觉这道题的思路其实是比较明显的,$DP$的方法显而易见
然后......我就开始了暴搜......
【正解】
先挂一个大佬的博客:orz
一道思路明显的$DP$优化题。
先对状态进行说明:$dp[i][j]$:在第 $i$ 天持有 $j$ 张股票时的最大收益。
首先,我们应该对信息相对于以往比较多的题目进行分析。
题意我不必概括,我们来说一说操作:
- 买入操作:可以在第 $i$ 天买入股票,每个股票的花费为 $AP_i$,并且最多不能买超过 $AS_i$
- 卖出操作:可以在第 $i$ 天卖出股票,每个股票的售价为 $BP_i$,并且最多不能卖超过 $BS_i$
- 偷懒操作:你的游戏情结蠢蠢欲动,然后你就和朋友们 $open black$ 去了,并且在股票上你啥都没干......
对于每一天,只可能会有这三种操作情况之一。并且前两种情况必须时隔 $W$ 天。
我们再对这三种情况展开:
- 在啥都没有的情况下买入股票:这种情况相当于初始化,此时 $dp[i][j]=-j×ap[i](j \in [0,as[i]])$
- 开黑去了,啥也没干:可以直接转移 $dp[i][j]=max\{dp[i][j],dp[i-1][j]\}$
- 在啥都没有的情况下卖出股票:你在想什么?货都没有还想交易?这种情况是不可能的,直接忽略。
- 在有股票时买股票:这种情况,我们需要对于天数进行讨论。首先,这种情况只需要从第 $i-W-1$ 天转移,那么问题来了,为什么不需要从第 $i-W-2$ 天,或者是更前面的天数转移呢?那是因为我们在第二种情况中,已经将前面所有的最优的情况往前面转移了,所以第 $i-W-1$ 天的状态实际上已经包含了前面的最优的状态了。并且,我们枚举的 $k$ 必须比 $j$ 小,因为我们这是买入股票。然后,$k$ 不能小于 $j-as_i$,因为我们在这一天最多买 $as_i$ 张股票,如果 $k<j-as_i$,也就是说即使是将能买的股票全买了都没有 $j$ 张多,那么怎么转移呢?所以就有了状态:$dp[i][j]=max\{dp[i][j],dp[i-W-1][k]-(j-k)×ap_i|k \in [j-as_i,j)\}$
- 在有股票时卖股票:这种情况与上一种很像,此处不再赘述,可自行思考,直接给出状转:$dp[i][j]=max\{dp[i][j],d[i-W-1][k]+(k-j)×bp_i|k \in (j,j+bs_i]\}$
对于上面的四种情况手动忽略第三种,我们有了四个状转,那么可以直接码代码,但是再一想,这个算法复杂度似乎在 $O(TP^2)$,对于这道题是肯定过不了的,考虑进行优化。
看看状转,我们发现这是区间最大值,单点修改,这是可行的思路,并且也可以过,但是或许太过于复杂。
考虑现有的 $DP$ 优化策略,结合其区间最大值转移,很容易想到单调队列,然后代码就出来了......
#include<bits/stdc++.h> using namespace std; template<class T>inline void qread(T& x){ char c;bool f=false;x=0; while((c=getchar())<'0'||'9'<c)if(c=='-')f=true; for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48)); if(f)x=-x; } template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);} inline int rqread(){ char c;bool f=false;int x=0; while((c=getchar())<'0'||'9'<c)if(c=='-')f=true; for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48)); return f?-x:x; } template<class T>inline T Max(const T x,const T y){return x>y?x:y;} template<class T>inline T Min(const T x,const T y){return x<y?x:y;} template<class T>inline T fab(const T x){return x>0?x:-x;} const int MAXT=2000; int T,P,W,ap,bp,as,bs,dp[MAXT+5][MAXT+5]; int Q[MAXT+5],tail,head; signed main(){ qread(T,P,W); memset(dp,128,sizeof dp); for(int i=1;i<=T;++i){ qread(ap,bp,as,bs); for(int j=0;j<=as;++j)dp[i][j]=-j*ap; for(int j=0;j<=P;++j)dp[i][j]=Max(dp[i][j],dp[i-1][j]); if(i<=W)continue; head=1,tail=0; for(int j=0;j<=P;++j){ 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; if(head<=tail)dp[i][j]=Max(dp[i][j],dp[i-W-1][Q[head]]+Q[head]*ap-j*ap); } head=1,tail=0; for(int j=P;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; if(head<=tail)dp[i][j]=Max(dp[i][j],dp[i-W-1][Q[head]]+Q[head]*bp-j*bp); } } int ans=-0x7f7f7f7f; for(int i=0;i<=P;++i)ans=Max(ans,dp[T][i]); printf("%d\n",ans); return 0; }