测试地址:Trade

题目大意:给定连续T天的股市情况,包含四个参数api,bpi,asi,bsi,表示第i天买价为api一股,卖价为bpi一股,当天最多能买asi股,最多能卖bsi股,一天之内只能在买和卖中选择一个,另外限制任何一天手中股票不能超过maxp股,如果在一天进行了交易(买或卖),那么接下来W天都不能交易。一开始有无限的本金,但是没有股票,求最后最多能赚多少钱。

做法:每一天的操作无非就是三种:不交易,买,卖。按照这个写出状态转移方程,设dp[i][j]为第i天结束后手中持有j股的最大收益,则:

dp[i][j]=max(dp[i-1][j],max(dp[x][k]-api*(j-k))(x≤i-w-1,k<j),max(dp[x][k]+bpi*(k-j))(x≤i-w-1,k>j));

初始化:dp[0][0]=0,dp[i][j]=-inf(i≠0且j≠0)。

注意到如果枚举x和k的话,这是一个O(T*maxp)的式子,那么总的复杂度将达到O(T^2*maxp^2),华丽爆炸。我们考虑枚举x的必要性,发现我们每次求dp[i][j]都要考察dp[i-1][j]的情况,也就是说dp[i][j]≥dp[i-1][j],所以在k相等的情况下,dp[x][k]在x取最大值时最大,所以x总是i-w-1,复杂度降低到O(T*maxp^2),仍然不能通过此题。再单独观察买和卖的式子,发现都能化成max(w[k])+c(l≤k≤r)的形式,对于买的式子,其中w[k]=dp[i-w-1][k]+api*k,c=-api*j,l=j-asi,r=j-1,卖的式子同理,我们发现在i和j相同的情况下,w[k]是由k唯一确定的变量(且可以O(1)算出),c是一个常数,且k的取值范围大小始终都是一个常数,这启示我们使用单调队列进行优化,于是复杂度降低到O(T*maxp),完美解决该题。

以下是本人代码:

 

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf 2000000000
using namespace std;
int tt,T,maxp,w,api[2010],bpi[2010],asi[2010],bsi[2010];
int dp[2010][2010],h,t,q[2010];

int main()
{
  scanf("%d",&tt);
  while(tt--)
  {
    scanf("%d%d%d",&T,&maxp,&w);
	for(int i=1;i<=T;i++)
	  scanf("%d%d%d%d",&api[i],&bpi[i],&asi[i],&bsi[i]);
	
	for(int i=0;i<=T;i++)
	  for(int j=0;j<=maxp;j++)
	    dp[i][j]=-inf;
	dp[0][0]=0;
	for(int i=1;i<=T;i++)
	{
	  int s=max(0,i-w-1);
	  h=1,t=0;
	  for(int j=0;j<=maxp;j++)
	  {
	    dp[i][j]=max(dp[i][j],dp[i-1][j]);
		if (j>0)
		{
		  while(h<=t&&q[h]<j-asi[i]) h++;
		  while(h<=t&&dp[s][j-1]+api[i]*(j-1)>=dp[s][q[t]]+api[i]*q[t]) t--;
	      q[++t]=j-1;
		  dp[i][j]=max(dp[i][j],dp[s][q[h]]-api[i]*(j-q[h]));
		}
	  }
	  h=1,t=0;
	  for(int j=maxp;j>=0;j--)
	  {
	    if (j<maxp)
		{
		  while(h<=t&&q[h]>j+bsi[i]) h++;
		  while(h<=t&&dp[s][j+1]+bpi[i]*(j+1)>=dp[s][q[t]]+bpi[i]*q[t]) t--;
	      q[++t]=j+1;
		  dp[i][j]=max(dp[i][j],dp[s][q[h]]+bpi[i]*(q[h]-j));
		}
	  }
	}
    
	int ans=0;
	for(int i=0;i<=maxp;i++)
	  ans=max(ans,dp[T][i]);
	printf("%d\n",ans);
  }
  
  return 0;
}