• 洛谷P2569 股票交易


    题目传送门https://www.luogu.org/problemnew/show/P2569

    第一眼看题就觉得是个dp ,然后看到2000的范围,hmm大概是个n^2的2维dp

    开始设状态,第一维肯定是天数的枚举,第二维...看着MaxP的范围,觉得应该是持有股票数量的枚举

    那么dp[i][j]就是对于1~i天,第i天持有j张股票的情况下最多能赚的钱

    然后想转移方程,分成几种情况

    case 1:前面都不买,从第i天开始买

      也就是从一无所有的情况下买j张股票dp[i][j]=-APi*j

    case 2:不买不卖

      这种情况下不会就不会受到W的限制,直接dp[i][j]=dp[i-1][j]

    case 3:发生买卖

      首先要考虑应该从前面哪一天的状态转移过来,也就是上一次买卖在哪一天发生。

      乍一看好像没办法知道应该从哪一天转移,莫非又多一层1~i-W-1的循环?

      其实不用。上一次买卖所获得的最大值可以根据case 2的dp方程向后转移

      所以直接从i-W-1天转移就行

      case 3.1:又卖又买

        仔细想想,这种情况不可能成为最大值。因为有APi>=BPi,卖了又买不如买少点,多折腾是不行的

      case 3.2:只买不卖

        既然是买,那么之前持有的股票必然比j少,枚举之前持有的股票k,花钱(j-k)*APi

        dp[i][j]=dp[i-W-1][k]-(j-k)*APi;

      case3.3:只卖不买

        与case 3.2类似

        dp[i][j]=max dp[i-W-1][k]+(k-j)*BPi

    大概就是这样。dp[T][0]就是最终答案(保险起见我写的是max dp[T][0~MaxP])

    时间大概是O(n*MaxP^2)

    预期50pt做法(实际70pt)

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    int T,MaxP,W;
    
    struct pig {
        int APi,BPi,ASi,BSi;
    } day[2005];
    
    int dp[2005][2005];
    
    int main() {
        cin>>T>>MaxP>>W;
        for(int i=1; i<=T; i++) {
            scanf("%d%d%d%d",&day[i].APi,&day[i].BPi,&day[i].ASi,&day[i].BSi);
        }
        for(int i=0; i<=T; i++) {
            for(int j=0; j<=MaxP; j++) {
                dp[i][j]=-999999999;
            }
        }
        dp[0][0]=0;
        
        for(int i=1;i<=T;i++){
            for(int j=0;j<=MaxP;j++){
                dp[i][j]=dp[i-1][j];
                if(j<=day[i].ASi){
                    
                    dp[i][j]=max(dp[i][j],-j*day[i].APi);
                }
                if(i-W-1<0){
                    continue;
                }
                for(int k=j-day[i].ASi;k<j;k++){
                    if(k<0){
                        continue;
                    }
                    dp[i][j]=max(dp[i][j],dp[i-W-1][k]-(j-k)*day[i].APi);
                }
                for(int k=j+1;k<=j+day[i].BSi;k++){
                    if(k>MaxP){
                        break;
                    }
                    dp[i][j]=max(dp[i][j],dp[i-W-1][k]+(k-j)*day[i].BPi);
                }
            }
        }
        int ans=0;
        for(int j=0; j<=MaxP; j++) {
            ans=max(ans,dp[T][j]);
        }
        cout<<ans;
        return 0;
    }
    /*
    输入
    初始化 赋-inf,dp[0][0]=0 
    dp循环
        i 天数1-T 
            j 当前持股数0-Maxp
                从当前天开买 j<=Asi dp[i][j]=-j*APi; 
                不买不卖 dp[i][j]=dp[i-1][j]
                如果i-W-1>=0
                    k 前次购买时持股数 买股 (j-Asi)~j&&>=0
                        dp[i][j]=max dp[i-W-1][k]-(j-k)*APi;
                    k 前次购买时持股数 卖股  j~(j+Bsi)&&<=MaxP
                        dp[i][j]=max dp[i-W-1][k]+(k-j)*BPi
    输出,max(dp[0][0~MaxP]) 
    
    自测数据 
    2 1
    1 1 1
    1 1 1
    2 1 1
    3 1 1
    4 1 1
    
    2 0
    1 1 1
    1 1 1
    2 1 1
    3 1 1
    4 1 1
    */
    View Code(附伪代码提纲)

     怎么继续优化呢?

    如果仍然保持dp[i][j]的形式,i和j的循环肯定是不能去掉的,所以考虑优化k

    用case 3.2:只买不卖 举例

    它的dp方程是dp[i][j]=max dp[i-W-1][k]-(j-k)*APi;

    乘法分配律得dp[i][j]=max dp[i-W-1][k]+k*APi-j*APi;

    j是枚举出来的,所以可以拆分一下max,得dp[i][j]=max(dp[i-W-1][k]+k*APi)-j*APi;

    因为对于每个确定的i和j,k属于区间 [j - ASi ,j-1] ,假设i是定值,ASi就是定值。那么对于所有j,k的区间大小是一样的

    一个长度固定的区间一格一格的移动...这不就是滑动窗口

    单调队列优化,O(n*MaxP),100pt到手

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    int T,MaxP,W;
    
    struct MonoQue {
        int lim;//维持的长度 
        int size;//最大存储量 
        int que[2005];//单调队列 
        int data[2005];//维护的数据
        int head,tail;
    
        void init(int mys,int mylim) {
            size=mys;
            lim=mylim;
            head=0;
            tail=0;
        }
        void push(int id) {//向单调队列中加入并维护,id可以在特殊情况下跳跃前进 
            if(id<=size) {//防越界处理 
                for(; head<tail;) {
                    if(data[que[tail-1]]>data[id]) {
                        break;
                    }
                    tail--;
                }
                que[tail]=id;
                tail++;
            }
            if(que[head]+lim<=id) {
                pop();
            }
        }
        void pop() {
            head++;
        }
        int quetop() {
            return que[head];
        }
        int top() {
            return data[quetop()];
        }
    };
    
    struct pig {
        int APi,BPi,ASi,BSi;
    } day[2005];
    
    int dp[2005][2005];
    
    int main() {
        cin>>T>>MaxP>>W;
        for(int i=1; i<=T; i++) {
            scanf("%d%d%d%d",&day[i].APi,&day[i].BPi,&day[i].ASi,&day[i].BSi);
        }
        for(int i=0; i<=T; i++) {
            for(int j=0; j<=MaxP; j++) {
                dp[i][j]=-999999999;
            }
        }
        dp[0][0]=0;
    
        MonoQue buy,sell;
        for(int i=1; i<=T; i++) {
            buy.init(MaxP,day[i].ASi+1);
            sell.init(MaxP,day[i].BSi+1);
            if(i-W-1>=0) {//防数组超下界
                for(int j=0; j<=MaxP; j++) {
                    buy.data[j]=dp[i-W-1][j]+j*day[i].APi;
                    sell.data[j]=dp[i-W-1][j]+j*day[i].BPi;
                }
            }
            for(int j=0; j<day[i].BSi; j++) {
                sell.push(j);//sell需要预先加入
            }
            for(int j=0; j<=MaxP; j++) {
                dp[i][j]=dp[i-1][j];
    
                if(j<=day[i].ASi) {
                    dp[i][j]=max(dp[i][j],-j*day[i].APi);
                }
                if(i-W-1<0) {
                    continue;
                }
                buy.push(j);
                dp[i][j]=max(dp[i][j],buy.top()-j*day[i].APi);
    
                sell.push(j+day[i].BSi);//单调队列里会处理超了MaxP的情况 
                    
                dp[i][j]=max(dp[i][j],sell.top()-j*day[i].BPi);
            }
        }
        int ans=0;
        for(int j=0; j<=MaxP; j++) {
            ans=max(ans,dp[T][j]);
        }
        cout<<ans;
        return 0;
    }
    /*
    自测数据
    3 2 0
    2 1 1 1
    3 2 1 1
    4 3 1 1
    */
    View Code

    参考:

    https://www.luogu.org/blog/Sooke/solution-p2569

  • 相关阅读:
    2.变量、数据类型、数据类型转换相关函数
    3.字符串、列表、元组、字典、集合的补充
    CentOS7安装mysql后无法启动服务,提示Unit not found
    (个人记录)Python2 与Python3的版本区别
    1.print()与input()
    JAVA:函数的定义、使用
    Java:包装类
    Java:访问权限
    Java程序流程控制:判断结构、选择结构、循环结构
    Java:运算符的问题
  • 原文地址:https://www.cnblogs.com/sun123zxy/p/luogu2569.html
Copyright © 2020-2023  润新知