zoukankan      html  css  js  c++  java
  • 洛谷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

  • 相关阅读:
    Educational Codeforces Round 10 C. Foe Pairs 水题
    Educational Codeforces Round 10 B. z-sort 构造
    CDOJ 1048 Bob's vector 三分
    Educational Codeforces Round 10 A. Gabriel and Caterpillar 模拟
    第14届电子科大初赛民间盗版部分题目题解
    HDU 5654 xiaoxin and his watermelon candy 离线树状数组 区间不同数的个数
    HDU 5653 Bomber Man wants to bomb an Array. dp
    HDU 5652 India and China Origins 二分+并查集
    HDU 5651 xiaoxin juju needs help 数学
    HDU 5650 so easy 数学
  • 原文地址:https://www.cnblogs.com/sun123zxy/p/luogu2569.html
Copyright © 2011-2022 走看看