• [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% 的数据,0leq W<Tleq 50,1leq ext{MaxP}leq500W<T50,1MaxP50

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

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

    对于所有的数据,1leq BP_ileq AP_ileq 1000,1leq AS_i,BS_ileq ext{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
    ",ans);//输出
        return 0;//不要忘了return 0;^_^
    }
    //然后AC
  • 相关阅读:
    March 13 2017 Week 11 Monday
    March 12 2017 Week 11 Sunday
    March 11 2017 Week 10 Saturday
    March 10 2017 Week 10 Friday
    Mrach 9 2017 Week 10 Thursday
    March 8 2017 Week 10 Wednesday
    玩转Sketch,不容错过的5大实用插件推荐
    网页设计排版中哪些元素最重要?
    5 个关键点!优化你的 UI 原型设计
    如何制作一个完美的错误提示信息
  • 原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/13435973.html
Copyright © 2020-2023  润新知