suxxsfe

一言(ヒトコト)

P2569 [SCOI2010]股票交易

https://darkbzoj.tk/problem/1855
https://www.luogu.com.cn/problem/P2569

单调队列优化,还是看了一眼题解才做出来的/kk

\(1\leq BP_i\leq AP_i\leq 1000,1\leq AS_i,BS_i\leq\text{MaxP}1\)
\(0\leq W<T\leq 2000,1\leq\text{MaxP}\leq20000\)


可以想到设计状态 \(f_{i,j}\) 表示前 \(i\) 天,有 \(j\) 个股票,的最大获利
分成这么几种情况来进行转移:

  1. 不买不卖
  2. 之前没有买过任何股票,直接买
  3. 之前没有买过任何股票,直接卖
  4. 从之前某个状态继续买
  5. 从之前某个状态继续买

显然,第三种情况不成立,因为没买过不能卖
分别讨论其它情况

第一种,\(f_{i,j}=f_{i-1,j}\)
第二种,显然 \(f_{i,j}=-AP_i\cdot j\)

三四种稍微复杂
肯定是从第 \(i-w+1\) 天转移而来,因为 \(i-w\sim i-1\) 都不符合限制,不行
但为什么不考虑 \(i-w+1\) 之前的?因为之前某一天对应的状态会通过第一种不买不卖的情况一路转移到第 \(i-w+1\)
看题解前就是因为这一点没想过来才卡着没做出来的
第三种的转移方程:

\[f_{i,j}=\max_{k=j-AS_i}^{j-1} f_{i-w-1,k}-(j-k)\cdot AP_i \]

不难理解,\(k\) 的限制条件是为了符合买入数量的显示,\(-(j-k)\cdot AP_i\) 就是买入要花的钱
同理,第四种也类似:

\[f_{i,j}=\max_{k=j+1}^{j+BS_i} f_{i-w-1,k}+(k-j)\cdot BP_i \]


但发现这样转移复杂度不行,还要优化一下
因为是取 \(\max\),而且还是一个不断整体移动且长度不变的区间(比如第三种情况是 \(j-AS_i\sim j-1\)),所以想到单调队列,其实是用标签搜进来的
给式子变形,以第三种为例

\[f_{i,j}=(\max_{k=j-AS_i}^{j-1} f_{i-w-1,k}+k\cdot AP_i)-j\cdot AP_i \]

就是用一下乘法分配然后把 \(-j\cdot AP_i\) 提出去
这样,每次把 \(f_{i-w-1,k}+k\cdot AP_i\) 放入队列,然后更新的时候就是用队列的最大值减去 \(-j\cdot AP_i\) 来取 \(\max\) 更新

第四种的式子:

\[f_{i,j}=(\max_{k=j+1}^{j+BS_i} f_{i-w-1,k}+k\cdot BP_i)-j\cdot BP_i \]

代码:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
int n,maxp,w;
int f[2005][2005];
int tail,head,num[2005],ind[2005];
int main(){
	n=read();maxp=read();w=read();
	std::memset(f,128,sizeof f);
	for(reg int ap,bp,as,bs,i=1;i<=n;i++){
		ap=read();bp=read();as=read();bs=read();
		for(reg int j=0;j<=as;j++) f[i][j]=-ap*j;//直接买 
		for(reg int j=0;j<=maxp;j++) f[i][j]=std::max(f[i][j],f[i-1][j]);//不买不卖
		if(i<=w) continue;
		//从原来某状态继续买
		tail=head=0;num[0]=f[i-w-1][0];ind[0]=0;
		for(reg int tmp,j=1;j<=maxp;j++){
			if(tail<=head) f[i][j]=std::max(f[i][j],num[tail]-j*ap);
			tmp=f[i-w-1][j]+j*ap;
			while(tail<=head&&num[head]<=tmp) head--;
			num[++head]=tmp;ind[head]=j;
			while(tail<=head&&ind[tail]<j+1-as) tail++;
		}
		//从原来的某状态继续卖
		//因为这个被转的区间是 [j+1,j+bs],所以倒叙循环 
		tail=head=0;num[0]=f[i-w-1][maxp]+bp*maxp;ind[0]=maxp;
		for(reg int tmp,j=maxp-1;~j;j--){
			if(tail<=head) f[i][j]=std::max(f[i][j],num[tail]-j*bp);
			tmp=f[i-w-1][j]+j*bp;
			while(tail<=head&&num[head]<=tmp) head--;
			num[++head]=tmp;ind[head]=j;
			while(tail<=head&&ind[tail]>j-1+bs) tail++;
		}
	}
	int ans=0;
	for(reg int i=0;i<=maxp;i++) ans=std::max(ans,f[n][i]);
	std::printf("%d",ans);
	return 0;
}
posted @ 2020-06-13 22:21  suxxsfe  阅读(152)  评论(0编辑  收藏  举报