股票交易 【SCOI 2010】HDU 3401 (单调队列优化DP入门)

来源:互联网 发布:基恩士plc编程软件 编辑:程序博客网 时间:2024/06/05 20:21

题目描述:
 最近 Lxhgww 又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。

 通过一段时间的观察,Lxhgww 预测到了未来 T 天内某只股票的走势,第 i 天的股票买入价为每股 APi,第 i 天的股票卖出价为每股 BPi数据保证对于每个i,都有APiBPi),但是每天不能无限制地交易,于是股票交易所规定第 i 天的一次买入至多只能购买ASi 股,一次卖出至多只能卖出 BSi 股。

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

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

输入:
 输入数据第一行包括3个整数,分别是 T,MaxP,W 。
 接下来 T 行,第 i 行代表第 i-1 天的股票走势,每行 4 个整数,分别表示 APiBPiASiBSi

输出:
 输出数据为一行,包括 1 个数字,表示 Lxhgww 能赚到的最多的钱数。

样例输入:
5 2 0
2 1 1 1
2 1 1 1
3 2 1 1
4 3 1 1
5 4 1 1

样例输出:
3

【数据范围】
对于 30% 的数据,0≤W<T≤50;1≤MaxP≤50 ;
对于 50% 的数据,0≤W<T≤2000;1≤MaxP≤50 ;
对于 100% 的数据,0≤W<T≤2000;1≤MaxP≤2000 ;
对于所有的数据,1≤BPi≤APi≤1000;1≤ASi,BSi≤MaxP 。

题目分析:
 读完题,我们可以根据题意将每天的情况分为3类,分别是:
  ①、及不买也不卖
  ②、买进一些股票
  ③、卖出一些股票
 
 因为求解最优解,我们可以定义 dp[ i ][ j ]表示第 i 天,有 j 只股票的最优解,同样分三种情况来讨论转移方程(分别对应上面三种情况):
  ①、dp[ i ][ j ] = dp[ i - 1 ][ j ]
  ②、dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ r ][ k ] - AS[ i ] * ( j-k ) )
  ③、dp[ i ][ j ] = max( dp[ i ][ j ] , dp[ r ][ k ] +BS[ i ]* ( k -j ) )
 这个转移 ,我们每次要枚举 i , j , r , k 才能转移,考虑降维度;
 
 首先我们想到,因为 r 必须满足 r<=i-w-1,对于i-w-1之前都可以通过方程①转移到 i-w-1天,所以我们就不需要枚举 r 转移了,每次记录一个前缀最大值即可;

 然后我们将方程②拆开:dp[ i ][ j ] =max(dp[ i ][ j] , dp[ i-w-1 ][ k ]+k*AS[i]-AS[i] * j,我们发现 dp[ i-w-1 ][ k ]+k*AS[i] 是一个与 j 完全无关的量,那么我们就可以每次计算的时候将 dp[ i-w-1 ][ k ]+k*AS[i] ,加入一个单调队列里维护对首,对于队尾小于它的就可以直接弹出队列,因为那些数答案既没有它优,而且对后面状态的影响更小,然后每次更新 dp[i][j]是就可以直接取出对首更新。
 
 这样转移变成了O(1)的,总时间复杂度为O(n2),可以解决这道题;

实际上单调队列优化DP就是像本题中找到一个与‘ j ’ (不同的题不同) 无关的量用单调队列维护,每次取出对首即为最优解

#include<bits/stdc++.h>using namespace std;#define inf 0xfffffff#define min(a,b) a<b?a:b#define max(a,b) a>b?a:bstruct node{    int pos;    int f;};int AP[2001],BP[2001],AS[2001],BS[2001];int dp[2001][2001];node q[2001];int head,tail;int n,MaxP,W;int main(){    int i,j,cas,nowf;        scanf("%d%d%d",&n,&MaxP,&W);        for(i=1;i<=n;i++)        {            scanf("%d%d%d%d",&AP[i],&BP[i],&AS[i],&BS[i]);        }        for(i=0;i<=n;i++)            for(j=0;j<=MaxP;j++)                dp[i][j]=-inf;        for(i=1;i<=W+1;i++)        {            for(j=0;j<=(min(MaxP,AS[i]));j++)                dp[i][j]=-AP[i]*j;        }        dp[0][0]=0;        for(i=1;i<=n;i++)        {            for(j=0;j<=MaxP;j++)                dp[i][j]=max(dp[i][j],dp[i-1][j]);            if(i<=W+1)                continue;            int pre=i-W-1; //队列里保存的是第i-W-1天的信息            head=tail=0;            for(j=0;j<=MaxP;j++) //因为是买,所以肯定越来越多,求DP[i][j]的时候,之前的0~~j-1的信息就存在在队列了            {                nowf=dp[pre][j]+j*AP[i];                while(head<tail && q[tail-1].f<nowf)                    tail--;                q[tail].f=nowf;q[tail++].pos=j;                while(head<tail && q[head].pos+AS[i]<j)                    head++;                dp[i][j]=max(dp[i][j],q[head].f-j*AP[i]);            }            head=tail=0;            for(j=MaxP;j>=0;j--) //因为是卖,道理同上            {                nowf=dp[pre][j]+j*BP[i];                while(head<tail && q[tail-1].f<nowf)                    tail--;                q[tail].f=nowf;q[tail++].pos=j;                while(head<tail && q[head].pos-BS[i]>j)                    head++;                dp[i][j]=max(dp[i][j],q[head].f-j*BP[i]);            }        }        int ans=0;        for(i=0;i<=MaxP;i++)            ans=max(ans,dp[n][i]);        printf("%d\n",ans);    return 0;}