[SCOI2010]股票交易

题意

题目描述

最近 $ 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}$ 能赚到的最多的钱数。

输入输出样例

输入样例#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\%$ 的数据,$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;
}
原文地址:https://www.cnblogs.com/autoint/p/10802530.html