*点击

[SCOI2010]股票交易(单调队列优化dp)

题目

Description

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

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

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

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

 对于 

30\%30% 的数据,0\leq W<T\leq 50,1\leq\text{MaxP}\leq500W<T50,1MaxP50

对于 50\%50% 的数据,0\leq W<T\leq 2000,1\leq\text{MaxP}\leq500W<T2000,1MaxP50

对于 100\%100% 的数据,0\leq W<T\leq 2000,1\leq\text{MaxP}\leq20000W<T2000,1MaxP2000

对于所有的数据,1\leq BP_i\leq AP_i\leq 1000,1\leq AS_i,BS_i\leq\text{MaxP}1BPiAPi1000,1ASi,BSiMaxP

Input

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

Output

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

Sample Input

5 2 0

2 1 1 1

2 1 1 1

3 2 1 1

4 3 1 1

5 4 1 1

Sample Output

3

思路

这是一道dp题;

我们设dp[i][j] 表示第 i 天 lxhgww手中拥有 j 个股票,能产生的最大money;

那么

dp初始化是

    memset(dp,-127/3,sizeof(dp));
    for(re ll j=0;j<=As[i];j++)
        dp[i][j]=-1*j*Ap[i];//第 i 天把 j 个股票全买光

 

转移方程是

$dp[i][j]=dp[i-1][j];$                                             

第 i 天啥也没做,money还是那么多;

$dp[i][j]=max(dp[i-w-1][k]-(j-k)*Ap[i]);$               

第 i 天买了些股票,money变少了 =_=

$dp[i][j]=max(dp[i-w-1][k]+(k-j)*Bp[i]);$               

第 i 天卖了些股票,money回来了¥_¥!!!

很显然在第二个转移方程里$ j-k 必须 < As[i]$     当天买的股票数不能超过题目中的限制;

很显然在第三个转移方程里$ k-j 必须 < Bs[i]$     当天卖的股票数不能超过题目中的限制;

 

但是我们发现,这一题如果直接dp写的话,会超时!!

那么就需要单调队列来优化;

观察两个dp 方程:

dp[i][j]=max(dp[i-w-1][k]-(j-k)*Ap[i]);

dp[i][j]=max(dp[i-w-1][k]+(k-j)*Bp[i]);

我们可以转化一下;

dp[i][j]=max(dp[i-w-1][k]+k*Ap[i]   -j*Ap[i]);

dp[i][j]=max(dp[i-w-1][k]+k*Bp[i]   -j*Bp[i]);

这样就可以把方程分成两部分;

我们发现划横线的部分只与  k 有关;

这样我们希望money最大,那么画线的部分就要最大;

 

这样我们就可以把 k 存到一个单调队列里;

找出最大的 $dp[i-w-1][k]+k*Ap[i];$ 就可以了;

然后什么时候踢队头呢;

在前面列出的dp方程中,我们标记了范围;

第二个转移方程里 $j-k 必须 < As[i]$ 

第三个转移方程里 $k-j 必须 < Bs[i] $

所以

 

1
while(head<=tail&&j-q[head]>As[i])
    head++;

2
while(head<=tail&&q[head]-j>Bs[i])
    head++;

 

这样就ok了

 

渣渣代码

#include<bits/stdc++.h>
#define re register
typedef long long ll;
using namespace std;
inline ll read()
{
    ll a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}//无处不在的快读
ll n,m,w;
ll q[10010],dp[2010][2010];
ll Ap[2010],Bp[2010],As[2010],Bs[2010];
int main()
{
    memset(dp,-127/3,sizeof(dp));
    n=read(); m=read(); w=read();
    for(re ll i=1;i<=n;i++)
    {
        Ap[i]=read(); Bp[i]=read();
        As[i]=read(); Bs[i]=read();//读入
    }
    for(re ll i=1;i<=n;i++)
    {
        for(re ll j=0;j<=As[i];j++)//注意j <=As[i];
            dp[i][j]=-1*j*Ap[i];//初始化 第 i 天把 j 个股票全买光
        for(re ll j=0;j<=m;j++)
            dp[i][j]=max(dp[i][j],dp[i-1][j]);//第 i 天啥也没做,money还是那么多;
        if(i-w-1<=0)//注意一定要加,判下标负数
            continue;
        ll head=1,tail=0;
        for(re ll j=0;j<=m;j++)
        {
            while(head<=tail&&j-q[head]>As[i])// j-k 必须 < As[i] 
                head++;//踢队头
            if(head<=tail)
            {
                ll k=q[head];
                dp[i][j]=max(dp[i][j],dp[i-w-1][k]-(j-k)*Ap[i]);//dp 转移方程
            }
            while(head<=tail&&dp[i-w-1][q[tail]]+q[tail]*Ap[i]<dp[i-w-1][j]+j*Ap[i])//找一个最大值
                tail--;
            q[++tail]=j;//入队
        }
        head=1,tail=0;
        for(re ll j=m;j>=0;j--)//我们需要 q[head](k) > j 所以先入队 大的j ,这样就可以满足q[head](k) > j 
        {
            while(head<=tail&&q[head]-j>Bs[i])// k-j 必须 < Bs[i] 
                head++;//踢队头
            if(head<=tail)
            {
                ll k=q[head];
                dp[i][j]=max(dp[i][j],dp[i-w-1][k]+(k-j)*Bp[i]);//dp 转移方程
            }
            while(head<=tail&&dp[i-w-1][q[tail]]+q[tail]*Bp[i]<dp[i-w-1][j]+j*Bp[i])//找一个最大值
                tail--;
            q[++tail]=j;//入队
        }
    }
    ll ans=1>>30;//方便的右移符号
    for(re ll i=0;i<=m;i++)
        ans=max(ans,dp[n][i]);//统计ans
    printf("%lld\n",ans);//输出
    return 0;//不要忘了return 0;^_^
}
//然后AC

 

 

posted @ 2020-08-04 21:42  木偶人-怪咖  阅读(115)  评论(0编辑  收藏  举报
*访客位置3D地图 *目录