ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P2569 [SCOI2010]股票交易 题解

2022-04-21 14:31:24  阅读:160  来源: 互联网

标签:P2569 int 题解 leq AP tail max inline SCOI2010


Post time: 2020-07-28 11:35:02

传送门

题意简述:

lxhgww 要通过买卖股票来赚最大的钱。他预测了 \(T\) 天的股票走势,每一天都有一个买入价 \(AP_i\) 和一个卖出价 \(BP_i\)(还有一个奇怪的限制 \(AP_i\geq BP_i\))。每天都有一个购买股票的上限和卖出股票的上限,分别为 \(AS_i\) 和 \(BS_i\)。还有两个大限制是:lxhgww 手中最多有 \(MaxP\) 股,并且如果第 \(i\) 天发生交易(买入或卖出),那么他第 \(i+1\) 到 \(i+w\) 天都不能交易。

题解:

题目条件很复杂,我们不要被它吓到,一点一点分析,最终积少成多就能轻松 AC 此题!

显然这应该是一道 DP。我们首先考虑 dp 数组的意义:为了满足无后效性,我们需要将题目中有关的条件都设置到 dp 数组的状态中。

于是我们得到:用 \(f_{i,j}\) 表示第 \(i\) 天,那个人手里有 \(j\) 支股票的最大利润(注意!因为一开始要买,所以可能为负数,一开始要赋成负无穷)。最终的结果就是在第 \(n\) 天手里的股票数为 \(0\sim MaxP\)的最大利润(即 \(\max_{j=0,j<=MaxP}f_{n,j}\))。

接下来我们的状态转移就需要分情况各个击破了。对于 \(f_{i,j}\) 来说,可以有这样 4 种转移:

  1. 在第 \(i\) 天一气买 \(j\) 个,即 \(f_{i,j}=\max(f_{i,j},-AP_i* j) (0\leq j\leq AS_i)\)

  2. 第 \(i\) 天什么也不干,即 \(f_{i,j}=\max(f_{i,j},f_{i-1,j}) (0\leq j\leq MaxP)\)

  3. 第 \(i\) 天买 \(k\) 只股票。因为每次交易要隔 \(w\) 天,所以最近一次的交易最晚要在第 \(i-w-1\) 天。又因为第 2 步,我们已经统计了每一天什么也不干的情况,所以 \(f_{i-w-1,j}\) 应当是在 \(1\) 到 \(i-w-1\) 天里交易的最大利润。即:\(f_{i,j}=max(f_{i,j},f_{i-w-1,k}-(j-k)\times AP_i)(j-AS_i\leq k<j)\)

  4. 第 \(i\) 天卖股票。同上,我们仍然要限制最晚要在 \(i-w-1\) 天交易。即:\(f_{i,j}=\max(f_{i,j},f_{i-w-1,k}+(k-j)\times BP_i)(j<k\leq j+BS_i)\)

上面的 (1)(2) 很好维护,我们将 (3)(4) 两式拆括号,可以得到:

\[\begin{aligned} f_{i,j}&=\max(f_{i-w-1,k}+k\times AP_i)-j\times AP_i(j-AS_i\leq k<j)\\ f_{i,j}&=\max(f_{i-w-1,k}+k\times BP_i)-j\times BP_i(j<k\leq j+BS_i)\\ \end{aligned} \]

很明显满足单调队列优化的要求:\(\max/\min\)中只含一次项(关于 \(k\)),对于我们枚举的每一个 \(i\),当 \(i>w\) 时,我们可以用一个单调队列记录 \(f_{i-w-1,k}+k\times AP_i\) 的最大值递减序列,从而让时间复杂度由 \(O(n^3)\) 降至 \(O(n^2)\)。

这样我们将一道复杂的问题拆成了四个小问题,并逐一分析得到解决办法之后,这道题就完成啦!

几个注意事项:

  1. 要注意什么时候可以用单调队列优化,什么时候不能用。
  2. 要明确单调队列里边记录的是下标!下标!!下标!!!
  3. 对于每个 \(i\),单调队列入队出队都可能不止一次,所以一定不要写成 if!要用 while
  4. 统计答案的时候,不要想当然,尽量涉及全面,有些题最后 f[n][m](意会)不一定是这道题的最优解。
  5. 数组下标越界是个很恶心的错误,要注意只要调用数组,就先判越界。

最后放上非常丑的代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=5000+4,INF=0x3f3f3f3f;
struct Queue{//使用手写队列来实现,我写了一个结构体,这样队列功能清晰一些 
	int qt[N],head,tail;
	inline void clear(){head=1,tail=0;}
	Queue(){clear();}
	inline void pop_front(){++head;}
	inline void pop_back(){--tail;}
	inline void push(int x){qt[++tail]=x;}
	inline int front(){return qt[head];}
	inline int back(){return qt[tail];}
	inline bool empty(){return head>tail;}
}q;
int n,m,w,ap,bp,as,bs;
int f[N][N];
int main(){
	memset(f,0xcf,sizeof(f));//不要忘了初始化成负无穷 
	scanf("%d%d%d",&n,&m,&w);
	for(int i=1;i<=n;++i){
		scanf("%d%d%d%d",&ap,&bp,&as,&bs);//完全可以边读入边做,省一点空间 
		for(int j=0;j<=as;++j) f[i][j]=-ap*j;//第一种子情况 
		for(int j=0;j<=m;++j) f[i][j]=max(f[i][j],f[i-1][j]);//第二种子情况 
		if(i<=w) continue;//后面要用i-w-1,数组不能越界 
		q.clear();//使用之前清空队列是个好习惯 
		for(int j=0;j<=m;++j){//第三种情况 
			while(!q.empty()&&j-q.front()>as) q.pop_front();
			while(!q.empty()&&f[i-w-1][q.back()]+q.back()*ap<=f[i-w-1][j]+j*ap) q.pop_back();
			q.push(j);
			if(!q.empty()) f[i][j]=max(f[i][j],f[i-w-1][q.front()]+q.front()*ap-j*ap);
		}
		q.clear();
		for(int j=m;j>=0;--j){//第四种情况 
			while(!q.empty()&&q.front()-j>bs) q.pop_front();
			while(!q.empty()&&f[i-w-1][q.back()]+q.back()*bp<=f[i-w-1][j]+j*bp) q.pop_back();
			q.push(j);
			if(!q.empty()) f[i][j]=max(f[i][j],f[i-w-1][q.front()]+q.front()*bp-j*bp);
		}
	}
	int ans=0;
	for(int i=0;i<=m;++i) ans=max(ans,f[n][i]);
	printf("%d\n",ans);//最终要卖完所有股票所以输出 
	return 0;
}

标签:P2569,int,题解,leq,AP,tail,max,inline,SCOI2010
来源: https://www.cnblogs.com/winterfrost/p/p2569-solution.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有