题意
题目描述
最近 $ ext{lxhgww}$ 又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。
通过一段时间的观察,$ ext{lxhgww}$ 预测到了未来 $T$ 天内某只股票的走势,第 $i$ 天的股票买入价为每股 $AP_i$,第 $i$ 天的股票卖出价为每股 $BP_i$(数据保证对于每个 $i$,都有 $AP_i geq BP_i$),但是每天不能无限制地交易,于是股票交易所规定第 $i$ 天的一次买入至多只能购买 $AS_i$ 股,一次卖出至多只能卖出 $BS_i$ 股。
另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔 $W$ 天,也就是说如果在第 $i$ 天发生了交易,那么从第 $i+1$ 天到第 $i+W$ 天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过 $ ext{MaxP}$。
在第 $1$ 天之前,$ ext{lxhgww}$ 手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,$T$ 天以后,$ ext{lxhgww}$ 想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?
输入输出格式
输入格式:输入数据第一行包括 $3$ 个整数,分别是 $T$,$ ext{MaxP}$,$W$。
接下来 $T$ 行,第 $i$ 行代表第 $i-1$ 天的股票走势,每行 $4$ 个整数,分别表示 $AP_i, BP_i, AS_i, BS_i$。
输出格式:输出数据为一行,包括 $1$ 个数字,表示 $ ext{lxhgww}$ 能赚到的最多的钱数。
输入输出样例
说明
对于 $30\%$ 的数据,$0leq W<Tleq 50,1leq ext{MaxP}leq50$
对于 $50\%$ 的数据,$0leq W<Tleq 2000,1leq ext{MaxP}leq50$
对于 $100\%$ 的数据,$0leq W<Tleq 2000,1leq ext{MaxP}leq2000$
对于所有的数据,$1leq BP_ileq AP_ileq 1000,1leq AS_i,BS_ileq ext{MaxP}$
分析
参照Sooke的题解,现在还有这种把思考过程放在网上的好人。
首先,不难想到本题可以用动态规划来解,这里就省略是如何想到动态规划的了。
动态规划的状态应该如何设?
观察数据范围,我们发现开一个 2000 × 2000 的数组来表示状态是没有问题的。
第一个维度,绝对用来表示是第几天,这个不用多想。
第二个维度,就需要稍微思考思考,我们发现它应该记录拥有多少张股票。
结合一下,整理出: (f_{i,j}) 表示第(i)天后拥有(j)张股票可以赚到的最多钱数。
动态规划的转移方程应该如何列?状态的初值怎么设?
分情况讨论。某一天,要么买股,要么卖股,要么不买也不卖。而买股又可分为两个情况,要么凭空买,跟前面交不交易没有任何关系,要么在前面交易的基础上买股,通俗一点说,也就是该状态是否需要由前面的状态转移而来。
那为什么卖股不用分为两个情况呢?仔细想想,怎么能凭空卖呢?既然和前面的贸易没有关系,也就是说手上没有任何一张股票,哪来的股票用来卖?
因此,一共有四种情况,其中只有一个情况并不需要其他状态转移,单独讨论:
1. 凭空买
仅本情况下的状态可以直接赋值,其他状态初值为 -inf,即:
(f_{i,j}=-ap_i imes j)
((0 leqslant j leqslant as_i))
下面三种情况,分别可列三个状态转移方程:
2. 不买也不卖
最好想也最好列转移方程的一种情况,直接由上一天转移,且拥有股票数量不变。即:
(f_{i,j})=(max(f_{i,j} \,, \, f_{i-1,j}))
3. 在之前的基础上买股票
这里的方程就比较复杂了,这里详细解释一下:
题目中指明说两次交易需要至少隔 (w) 天。也就是说,今天是第 (i) 天交易,那么上一次交易最近是在第 (i - w - 1) 天。
可我第 (i - w - 1) 天之前也可以交易啊?为什么偏偏是那一天,而不是第 (i - w - 2) 天?
回看刚刚讨论的第 2 种情况,我们已经把某一天以前的最优答案转移到了该天,所以从那一天转移,相当于从那一天包括前面任何一天开始转移,省去了大把时间。
接下来,我们已知第 (i) 天拥有 (j) 张股票,假设第 (i - w - 1) 天拥有 (k) 张股票。
(k) 一定比 (j) 小,因为这一种情况是买股票,股票只能多。
但股票又不能买太多,限制最多为 (as) 张。所以 (j - as_i) 是底线。
继续,这次交易买了 (j - k) 张股票,要花去 ((j - k) imes ap_i) 元。
整理一下,转移方程为:
(f_{i,j})=(max(f_{i,j} \,, \,f_{i-w-1,k}-(j-k) imes ap_i))
((j - as_i leqslant k < j))
4. 在之前的基础上卖股票
和上面的情况特别类似,在此简单地说一下区别。
因为是卖股票,(k) 这次要比 (j) 大。但要小于等于 (j + bs_i)。
这次交易卖了 (k - j) 张邮票,赚得 ((k - j) imes bp_i) 元。
整理一下,转移方程为:
(f_{i,j})=(max(f_{i,j} \,, \,f_{i-w-1,k}+(k-j) imes bp_i))
((j < k leqslant j + bs_i))
为什么可以使用单调性优化?
上面的 3,4 两种情况,列起来头头是道,但我们深入计算,发现时间复杂度是三次方级的。很显然,我们要想尽办法,把时间复杂度降到二次方级。
这个时候发现那两种情况,实际上可以使用单调性优化。此时达到降时间复杂度的目标。
就以第 3 种情况,在之前的基础上买股票为例子吧。
再重复提一下那个转移方程:
(f_{i,j})=(max(f_{i,j} \,, \,f_{i-w-1,k}-(j-k) imes ap_i))
((j - as_i leqslant k < j))
运用乘法分配率:
(f_{i,j})=(max(f_{i,j} \,, \,f_{i-w-1,k}-j imes ap_i+k imes ap_i))
((j - as_i leqslant k < j))
既然要将状态转移给 (f_{i,j}),此时 (j) 可以从 (max) 中提取出,此时转移方程变为:
(f_{i,j})=(max(f_{i,j} \,, \,f_{i-w-1,k}+k imes ap_i)-j imes ap_i)
((j - as_i leqslant k < j))
此时,我们发现以上的转移方程符合单调性优化的条件,故可以使用单调性优化。
没有理解地,可以这么想:由于转移有这么一个区间前提:((j - as_i leqslant k < j)),又因为转移方程中出现了 max,也就是说转移的目的是从该区间中,找到某个最大的值,基础好的你应该已经发现这就是滑动窗口了吧?只不过放进单调队列的元素,是 (f_{i-w-1,k}+k imes ap_i),但这并不影响转移。
那么第 4 种情况的转移,我就不多解释了,它的转移方程可以整理为:
(f_{i,j})=(max(f_{i,j} \,, \,f_{i-w-1,k}+k imes bp_i)-j imes bp_i)
((j < k leqslant j + bs_i))
好像除了后面的转移区间要求,两个整理后的方程几乎都是一样的。
对了,还有一个细节,是第 3 种情况转移应该顺序,第 4 种情况转移应该逆序,这个不难理解,先自己想想吧。
时间复杂度(O(n^2))
代码
#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;
co int N=2e3+1;
int f[N][N],q[N];
int main(){
int t,maxp,w;
read(t),read(maxp),read(w);
memset(f,0xcf,sizeof f);
for(int i=1;i<=t;++i){
int ap,bp,as,bs;
read(ap),read(bp),read(as),read(bs);
for(int j=0;j<=as;++j) f[i][j]=-j*ap;
for(int j=0;j<=maxp;++j) f[i][j]=max(f[i][j],f[i-1][j]);
if(i<=w) continue;
int l=1,r=0; // empty set
for(int j=0;j<=maxp;++j){
while(l<=r&&q[l]<j-as) ++l;
while(l<=r&&f[i-w-1][q[r]]+q[r]*ap<=f[i-w-1][j]+j*ap) --r;
q[++r]=j;
if(l<=r) f[i][j]=max(f[i][j],f[i-w-1][q[l]]+(q[l]-j)*ap);
} // here judge is not essential
l=1,r=0;
for(int j=maxp;j>=0;--j){
while(l<=r&&q[l]>j+bs) ++l;
while(l<=r&&f[i-w-1][q[r]]+q[r]*bp<=f[i-w-1][j]+j*bp) --r;
q[++r]=j;
if(l<=r) f[i][j]=max(f[i][j],f[i-w-1][q[l]]+(q[l]-j)*bp);
}
}
int ans=0;
for(int i=0;i<=maxp;++i) ans=max(ans,f[t][i]);
printf("%d
",ans);
return 0;
}