洛谷P2569 [SCOI2010] 股票交易

洛谷P2569 [SCOI2010] 股票交易

题目描述

最近 lxhgww\text{lxhgww}lxhgww 又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。

通过一段时间的观察,lxhgww\text{lxhgww}lxhgww 预测到了未来 TTT 天内某只股票的走势,第 iii 天的股票买入价为每股 APiAP_iAPi,第 iii 天的股票卖出价为每股 BPiBP_iBPi(数据保证对于每个 iii,都有 APi≥BPiAP_i \geq BP_iAPiBPi),但是每天不能无限制地交易,于是股票交易所规定第 iii 天的一次买入至多只能购买 ASiAS_iASi 股,一次卖出至多只能卖出 BSiBS_iBSi 股。

另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔 WWW 天,也就是说如果在第 iii 天发生了交易,那么从第 i+1i+1i+1 天到第 i+Wi+Wi+W 天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过 MaxP\text{MaxP}MaxP

在第 111 天之前,lxhgww\text{lxhgww}lxhgww 手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,TTT 天以后,lxhgww\text{lxhgww}lxhgww 想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?

输入格式

输入数据第一行包括 333 个整数,分别是 TTTMaxP\text{MaxP}MaxPWWW

接下来 TTT 行,第 iii 行代表第 i−1i-1i1 天的股票走势,每行 444 个整数,分别表示 APi, BPi, ASi, BSiAP_i,\ BP_i,\ AS_i,\ BS_iAPi, BPi, ASi, BSi

输出格式

输出数据为一行,包括 111 个数字,表示 lxhgww\text{lxhgww}lxhgww 能赚到的最多的钱数。

样例 #1

样例输入 #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%30\%30% 的数据,0≤W<T≤50,1≤MaxP≤500\leq W<T\leq 50,1\leq\text{MaxP}\leq500W<T50,1MaxP50
  • 对于 50%50\%50% 的数据,0≤W<T≤2000,1≤MaxP≤500\leq W<T\leq 2000,1\leq\text{MaxP}\leq500W<T2000,1MaxP50
  • 对于 100%100\%100% 的数据,0≤W<T≤2000,1≤MaxP≤20000\leq W<T\leq 2000,1\leq\text{MaxP}\leq20000W<T2000,1MaxP2000
  • 对于所有的数据,1≤BPi≤APi≤1000,1≤ASi,BSi≤MaxP1\leq BP_i\leq AP_i\leq 1000,1\leq AS_i,BS_i\leq\text{MaxP}1BPiAPi1000,1ASi,BSiMaxP

思路:动态规划

看到最优解问题,大概率贪心和动态规划出一个,贪心显然没有可能(可以构造hackhackhack),那就只有动态规划了。

状态设置

注意到数据范围都在200020002000以内,可以推测时间和空间复杂度均为O(N2)O(N^2)O(N2)

所以dpdpdp数组应当有两维,第一维显然是天数,第二维一定要与股票有关,于是自然设出持有股票数量。

所以dpi,jdp_{i,j}dpi,j表示第iii天持有jjj张股票时的最大赚钱数。

状态转移

首先明确一个基础事实,就是一定有dpi,j≥dpi−1,j≥dpi−2,j≥...≥dp1,jdp_{i,j}\ge dp_{i-1,j}\ge dp_{i-2,j}\ge ...\ge dp_{1,j}dpi,jdpi1,jdpi2,j...dp1,j

分析题目可得,第iii天可以进行333种操作:买入,卖出或既不买入也不卖出。

最好处理的是既不买入也不卖出,此时dpi,j=dpi−1,jdp_{i,j}=dp_{i-1,j}dpi,j=dpi1,j

然后是卖出,有dpi,j(i∈[W,T])=maxk∈[j,min(MaxP,j+BSi)](dpi−w−1,k+(k−j)BPi)dp_{i,j}(i\in[W,T])=\mathop{max}\limits_{k\in[j,min(MaxP,j+BS_i)]}(dp_{i-w-1,k}+(k-j)BP_i)dpi,j(i[W,T])=k[j,min(MaxP,j+BSi)]max(dpiw1,k+(kj)BPi)

买入和卖出是一样的dpi,j(i∈[W,T])=maxk∈[max(0,j−ASi),j](dpi−w−1,k−(j−k)APi)dp_{i,j}(i\in[W,T])=\mathop{max}\limits_{k\in[max(0,j-AS_i),j]}(dp_{i-w-1,k}-(j-k)AP_i)dpi,j(i[W,T])=k[max(0,jASi),j]max(dpiw1,k(jk)APi)

特别的,如果是第一次买入,即该次买入操作之前没有任何操作,可以写作dpi,j=−APi⋅j(j∈[0,ASi])dp_{i,j}=-AP_i\cdot j(j\in[0,AS_i])dpi,j=APij(j[0,ASi])

单调优化

容易发现,上述思路直接写作代码有i,j,ki,j,ki,j,k三层循环,复杂度达到O(n3)O(n^3)O(n3)数量级,所以考虑将kkk循环优化为常数级。

含有kkk循环的两个式子的逻辑是一样的,所以下面取卖出的为例子,买入同理即可:

首先将与kkk无关的式子提到maxmaxmax外面:dpi,j(i∈[W,T])=maxk∈[j,min(MaxP,j+BSi)](dpi−w−1,k+kBPi)−jBPidp_{i,j}(i\in[W,T])=\mathop{max}\limits_{k\in[j,min(MaxP,j+BS_i)]}(dp_{i-w-1,k}+kBP_i)-jBP_idpi,j(i[W,T])=k[j,min(MaxP,j+BSi)]max(dpiw1,k+kBPi)jBPi

那么要动态维护的就是区间[j,min(MaxP,j+BSi)][j,min(MaxP,j+BS_i)][j,min(MaxP,j+BSi)]dpi−w−1,k+kBPidp_{i-w-1,k}+kBP_idpiw1,k+kBPi的最大值。

那就是一个长度为ASiAS_iASi的滑动窗口问题,总时间复杂度变为O(N2)O(N^2)O(N2)

代码实现

变量定义(下述代码中的数组变量名与此相同):

#include<iostream>
#include<cstring>

const int N=2e3+10;

int T,p,w;//T,MaxP,W 
int as[N],bs[N],ap[N],bp[N];//AS_i,BS_i,AP_i,BP_i
int q[N],l,r;//单调队列,l为尾,r为头
int dp[N][N];//dp数组

输入,赋初值

cin>>T>>p>>w;
for(int i=1;i<=T;i++)
	cin>>ap[i]>>bp[i]>>as[i]>>bs[i];
memest(dp,0xcf,sizeof(dp));

小技巧:在memset中,可以赋0x3finfinfinf0xcf表示−inf-infinf(仅限有符号整形,无符号和浮点不行)

核心代码

方便拆解,这里iii循环省略,套在这部分代码外面即可:

最好处理的两个初值就是不买入也不卖出第一次操作买入

for(int j=0;j<=as[i];j++)
	dp[i][j]=-ap[i]*j;
for(int j=0;j<=p;j++)
	dp[i][j]=max(dp[i][j],dp[i-1][j]);

接下来的处理都有定义i−w≥0i-w\ge0iw0,于是先加continuecontinuecontinue

if(i-w<=0)
	continue;

接下来写卖出部分的滑动窗口,值得注意的是,我们平时写的滑动窗口是以当前元素结尾的窗口,但上面总结的问题为以当前元素开始的窗口,为了方便抄模板处理边界,所以考虑倒序处理:

l=1,r=0;
for(int j=p;j>=0;j--)
{
	while(l<=r&&q[l]>j+bs[i])
		l++;
	while(l<=r&&dp[i-w-1][q[r]]+q[r]*bp[i]<=dp[i-w-1][j]+j*bp[i])
		r--;
	q[++r]=j;
	if(l<=r)
		dp[i][j]=max(dp[i][j],dp[i-w-1][q[l]]+(q[l]-j)*bp[i]);
}

买入部分的滑动窗口和上面思路一样,只是这里又是顺序遍历,并且代码复制粘贴后正负号等地方要改,十分不建议Crtl+C/VCrtl+\mathbb{C/V}Crtl+C/V,不然改漏一个就是半小时……

l=1,r=0;
for(int j=0;j<=p;j++)
{
	while(l<=r&&q[l]<j-as[i])
		l++;
	while(l<=r&&dp[i-w-1][q[r]]+q[r]*ap[i]<=dp[i-w-1][j]+j*ap[i])
		r--;
	q[++r]=j;
	if(l<=r)
		dp[i][j]=max(dp[i][j],dp[i-w-1][q[l]]+(q[l]-j)*ap[i]);
}

输出

正常来说循环每一个dpT,jdp_{T,j}dpT,j找最大值即可,但理论上这里可以偷个小懒,直接输出dpT,0dp_{T,0}dpT,0

原理就是一个小贪心,如果结束时我手中还有股票没有抛售,那么在此前我直接不买显然更好。

最终代码

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N=2e3+10;

int T,p,w;
int as[N],bs[N],ap[N],bp[N];
int q[N],l,r;
int dp[N][N];

int main()
{
	cin>>T>>p>>w;
	for(int i=1;i<=T;i++)
		cin>>ap[i]>>bp[i]>>as[i]>>bs[i];
	memset(dp,0xcf,sizeof(dp));
	for(int i=1;i<=T;i++)
	{
		for(int j=0;j<=as[i];j++)
			dp[i][j]=-1*ap[i]*j;
		for(int j=0;j<=p;j++)
			dp[i][j]=max(dp[i][j],dp[i-1][j]);
		if(i<=w)
			continue;
		l=1,r=0;
		for(int j=0;j<=p;j++)
		{
			while(l<=r&&q[l]<j-as[i])
				l++;
			while(l<=r&&dp[i-w-1][q[r]]+q[r]*ap[i]<=dp[i-w-1][j]+j*ap[i])
				r--;
			q[++r]=j;
			if(l<=r)
				dp[i][j]=max(dp[i][j],dp[i-w-1][q[l]]+(q[l]-j)*ap[i]);
		}
		l=1,r=0;
		for(int j=p;j>=0;j--)
		{
			while(l<=r&&q[l]>j+bs[i])
				l++;
			while(l<=r&&dp[i-w-1][q[r]]+q[r]*bp[i]<=dp[i-w-1][j]+j*bp[i])
				r--;
			q[++r]=j;
			if(l<=r)
				dp[i][j]=max(dp[i][j],dp[i-w-1][q[l]]+(q[l]-j)*bp[i]);
		}
	}
//	int res=-0x3f3f3f3f;
//	for(int i=0;i<=p;i++)
//		res=max(res,dp[T][i]);
	cout<<dp[T][0]<<endl; 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值