洛谷P2569
这个题确实妙。
Description
给定天数 \(T\),并给出这 \(T\) 天内每天的股票买入卖出单价和数量限制;
给出天数 \(w\),表示每两次交易之间至少要隔 \(w\) 天;
给出参数 \(\text{MaxP}\),表示任何时候手中最多持有数量为 \(\text{MaxP}\) 的股票;
在初始资金无限且初始股票为零的条件下,求出 \(T\) 天之后能赚到的最多的钱。
Solution
乍一看信息很多很杂,其实只是情况比较多而已。
对于每一天,我们可以进行如下操作:
- 买入一部分股票;
- 卖出一部分股票;
- 不买也不卖。
然后我们分情况讨论即可。
看到题目中所有用的信息无非就是天数和金钱,而我们要做的就是表示出每一天的股票进出关系,所以我们可以设 \(f_{i,j}\) 表示在第 \(i\) 天手里有数量为 \(j\) 的股票时获得的最大价值。
然后方程式就比较显然了(大概吧)。
注:以下变量名除了特别说明外,其余均与原题中的同义。
买入股票时
其实也要分为两种情况
一种是凭空买股票,也就是原先手中无股票时买股票。
此时
表示第 \(i\) 天一开始买入了这些股票,只有支出没有收入。这同时也是第 \(i\) 天的 DP 数组的初始化。
另一种是手中一开始有股票,并且又买进了新的股票。
此时
表示第 \(w\) 天前的股票数为 \(k\),也就是说这一天的初始股票数为 \(k\),并且又买进了 \(j-k\) 数量的股票时所获得的最大价值。
但是我们要更新的是 \(j\),所以这个式子又可以化成这样
然后我们就可以发现这个式子非常眼熟,好像可以用来做单调队列优化。
但是这个我们一会再说,这里先把转移方程式说完。
卖出股票时
其实和买入差不多,但是手中没股票时买不了,所以只能从原先有股票的状态转移过来,也就是从第 \(w\) 天前的那个状态转移(在这一天之前的状态以及转移到了这一天)。
此时
表示原先有 \(k\) 数量的股票,然后又卖出了 \(k-j\) 数量的股票所获得的最大价值。
同上,这个式子也可以化成这样
注意买入和卖出的方程式中 \(k\) 和 \(j\) 的大小关系不同。
不买也不卖时
这个比较显然,因为没有进行任何交易,所以也不存在 \(w\) 天的限制,直接从前一天转移就好了。
方程式推完,问题已经解决一大半了。
但是,如果用上面推出来的方程式直接做显然会超时,因为它们的时间复杂度都是三次方级的。因此我们要想办法优化来使复杂度降幂。
然后我们上面推出来的那些符合单调队列优化形式的公式就排上用场了。
有了化简后的方程,实现就比较容易了,就不多说了。
Code
#include<queue>
#include<bits/stdc++.h>
#define maxn 2010
#define INF 0x3f3f3f3f
//#define int long long
using namespace std;
int t,maxp,w,ans,tail,head;
int f[maxn][maxn],q[maxn<<3];
int ap[maxn],bp[maxn],as[maxn],bs[maxn];
//deque<int> q;
int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
return s*w;
}
//不买也不卖:f[i][j]=max{f[i][j],f[i-1][j]}
//买入:f[i][j]=max{f[i-w-1][k]+k*ap[i]}-j*ap[i] (j-as[i]<=k<=j-1)
//卖出:f[i][j]=max{f[i-w-1][k]+k*bp[i]}-j*bp[i] (j+1<=k<=j+bs[i])
int main(){
memset(f,-0x7f,sizeof f);
t=read();maxp=read();w=read();
for(int i=1;i<=t;i++)
ap[i]=read(),bp[i]=read(),
as[i]=read(),bs[i]=read();
for(int i=0;i<=t;i++) f[i][0]=0;
for(int i=1;i<=t;i++){
for(int j=0;j<=as[i];j++) f[i][j]=-ap[i]*j;
for(int j=maxp;j>=0;j--) f[i][j]=max(f[i][j],f[i-1][j]);
if(i-w-1>=0){
head=1;tail=0;
for(int j=0; j<=maxp; j++) {
while(head<=tail&&q[head]<j-as[i]) head++;
while(head<=tail&&f[i-w-1][j]+ap[i]*j>=f[i-w-1][q[tail]]+ap[i]*q[tail]) tail--;
q[++tail]=j;f[i][j]=max(f[i][j],f[i-w-1][q[head]]+ap[i]*q[head]-j*ap[i]);
}
head=1;tail=0;
for(int j=maxp; j>=0; j--) {
while(head<=tail&&q[head]>j+bs[i]) head++;
while(head<=tail&&f[i-w-1][j]+bp[i]*j>=f[i-w-1][q[tail]]+bp[i]*q[tail]) tail--;
q[++tail]=j;f[i][j]=max(f[i][j],f[i-w-1][q[head]]+bp[i]*q[head]-j*bp[i]);
}
}
}
for(int i=0;i<=maxp;i++) ans=max(ans,f[t][i]);
printf("%d\n",ans);return 0;
}