hdu 3401 单调队列优化dp

本文探讨了一位热衷于股票市场的用户通过研究模式,预测未来股票走势,并利用特定规则进行交易,以最大化收益的过程。包括设定交易区间、股票购买和出售限制,以及与上一次交易的间隔要求。通过详细解析输入参数和输出结果,展示了如何计算在给定条件下获得的最大可能利润。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Trade

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3588    Accepted Submission(s): 1161


Problem Description
Recently, lxhgww is addicted to stock, he finds some regular patterns after a few days' study.
He forecasts the next T days' stock market. On the i'th day, you can buy one stock with the price APi or sell one stock to get BPi.
There are some other limits, one can buy at most ASi stocks on the i'th day and at most sell BSi stocks.
Two trading days should have a interval of more than W days. That is to say, suppose you traded (any buy or sell stocks is regarded as a trade)on the i'th day, the next trading day must be on the (i+W+1)th day or later.
What's more, one can own no more than MaxP stocks at any time.

Before the first day, lxhgww already has infinitely money but no stocks, of course he wants to earn as much money as possible from the stock market. So the question comes, how much at most can he earn?
 

Input
The first line is an integer t, the case number.
The first line of each case are three integers T , MaxP , W .
(0 <= W < T <= 2000, 1 <= MaxP <= 2000) .
The next T lines each has four integers APi,BPi,ASi,BSi( 1<=BPi<=APi<=1000,1<=ASi,BSi<=MaxP), which are mentioned above.
 

Output
The most money lxhgww can earn.
 

Sample Input
  
  
1 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
 

Author
lxhgww
 

Source
 


提交情况:WA WA WA WA WA WA WA WA WA WA WA ....AC

题目大意:
有个人买卖股票来赚钱,
给出一个交易区段的天数t , 有一个可以购买的股票数量上限maxp,并且当前交易与上一次交易必须 间隔W天
接着会给出每天的股票的买入卖出价格ap[i],bp[i] , 接着每天买入卖出的股票数量限制as[i],bs[i]。
求到了第t天可以获得的最大利润。

解题思路:
总的思路是
前W+1天【1,W+1】,只能够进行两种状态转移:j>0时从前一天直接转移,不买不卖;还有从前一天的j==0转移到今天的j>0,
也就是今天第一次买股票,买了j支。
剩余的时间,除了以上两种状态转移之外,还可以有从第i-W-1天的已有的j-k支股票的前提下,再买入k支股票。

此外还需要注意初始化的问题,
(1)一开始做的时候将dp全都初始化为了0,这样的话结果一定是错的,因为dp小于0的时候卖出股票之后收益可能会比dp=0的时候要大。
正确的做法应该是赋值为无穷小。
(2)还有,dp[1][0]必然就是0的了,前W+1天的dp[i][0]都是0,因为绝对不可能进行卖出交易。而后面的dp[i][0]就不一定是零,可能是刚刚把股票卖完了。


反省一下,wa了这么多次,主要还是因为自己的下标处理能力太差。出现的错误有head和tail的初始化写反了,还有上面说的初始化的问题,
还有后来优先队列删除末尾节点的地方把tail写成了head。还有一个自觉得很重要的错误,在第二份ac代码中会有标出。



第一遍先用普通朴素的dp做了一遍,T了
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <numeric>
#include <functional>
#define maxn 2015

using namespace std;
int ap[maxn],bp[maxn],as[maxn],bs[maxn];
int dp[maxn][maxn];
int n,maxp,W,ans;

int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        memset(dp,0,sizeof(dp));
        ans=0;
        scanf("%d %d %d",&n,&maxp,&W);
        for(int i=1;i<=n;i+=1){
            scanf("%d %d %d %d",&ap[i],&bp[i],&as[i],&bs[i]);
        }

        for(int i=1;i<=maxp;i+=1){
            dp[1][i]=dp[1][i-1]-ap[i];
        }
        for(int i=2;i<=n;i+=1){
            for(int j=0;j<=maxp;j+=1){
                dp[i][j]=max(dp[i-1][0]-j*ap[i],dp[i-1][j]);
                if(i>W+1){
                    //dp[i][j]=max(dp[i-1][0]-j*ap[i],dp[i-1][j]);
                    for(int k=1;k<=j&&k<=as[i];k+=1){ /*买进*/
                    //for(int k=1;j+k<=maxp&&k<=as[i];k+=1){
                        dp[i][j]=max(dp[i][j],dp[i-W-1][j-k]-ap[i]*k);
                        //dp[i][j]=max(dp[i][j],dp[i-W-1][j+k]-ap[i]*k);
                    }
                    for(int k=1;j+k<=maxp&&k<=bs[i];k+=1){  /*卖出*/
                    //for(int k=1;k<=j&&k<=bs[i];k+=1){
                        dp[i][j]=max(dp[i][j],dp[i-W-1][j+k]+bp[i]*k);
                        //dp[i][j]=max(dp[i][j],dp[i-W-1][j-k]+bp[i]*k);
                    }
                }
                //else{
                    //dp[i][j]=dp[i-1][j];
                    //dp[i][j]=max(dp[i-1][j],dp[i-1][0]-j*ap[i]);
                //}

                if(i==n){
                    ans=max(ans,dp[i][j]);
                }
            }
        }

        printf("%d\n",/*dp[n][0]*/ans);
    }
    return 0;
}

第二遍,用单调队列优化,wa了很多,但最后过了(下面是注释版和简明版两个版本):
<pre name="code" class="cpp">#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <numeric>
#include <functional>
#define maxn 2052

using namespace std;
int ap[maxn],bp[maxn],as[maxn],bs[maxn];
int dp[maxn][maxn];
int q[maxn],J[maxn];
int n,maxp,W,ans;

int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        memset(dp,128,sizeof(dp));
        //原来错了,因为没有初始化无穷小,这里也导致了8到3的蜕变
        //想想在i=n-1的某种情况下,从q里取出的值暂时是负的,
        //如果初始化为0这个时候就就不会选择负的数了
        ans=0;
        scanf("%d %d %d",&n,&maxp,&W);
        for(int i=1;i<=n;i+=1){
            scanf("%d %d %d %d",&ap[i],&bp[i],&as[i],&bs[i]);
        }

        /*dp[1][0]=0;
        for(int i=1;i<=maxp;i+=1){
            dp[1][i]=dp[1][i-1]-ap[1];
        }*/
        dp[0][0]=0;
        for(int i=1;i<=W+1;i+=1){
            for(int j=0;j<=maxp&&j<=as[i];j+=1){ //因为是前W+1天只能买一次,所以必须加上一个j<=as[i]的限制
                dp[i][j]=-j*ap[i];
            }
        }
        for(int i=1;i<=n;i+=1){
            for(int j=0;/*i>=W+2&&*/j<=maxp;j+=1){
            //这里不能有注释掉的语句,因为虽然当天不能卖那么多的股票,但是可以从昨天转移过来
                dp[i][j]=max(dp[i][j],dp[i-1][j]);
            }
            if(i<W+2) continue;    //因为这里没有continue会re
            int tail=0,head=1;      //这里有错原来是  int tail=1,head=0; 发生了从8到3的蜕变
            for(int j=0;j<=maxp;j+=1){  /*买进*/
                while(/*j-J[head]<as[i]*/J[head] + as[i] < j&&head<=tail) head+=1;  //维护单调队列的长度
                int newdp=dp[i-W-1][j]+j*ap[i];
                while(head<=tail&&newdp>q[tail]) tail-=1;
                tail+=1;
                q[tail]=newdp,J[tail]=j;
                //j[tail]这一步很重要,因为是多重背包,每一次入货出货都有限制,
                //而由于使用单调队列取得的最优解不一定符合限制条件,会过时

                //while(/*j-J[head]<as[i]*/J[head] + as[i] < j&&head<=tail) head+=1;  //维护单调队列的长度
                if(head<=tail) dp[i][j]=max(dp[i][j],q[head]-j*ap[i]);
            }

            tail=0,head=1;//这里有错原来是  int tail=1,head=0;
            for(int j=maxp;j>=0;j-=1){  /*卖出*/
                while(/*J[head]-j<bs[i]*/J[head] - bs[i] > j&&head<=tail) head+=1;  //维护单调队列的长度
                int newdp=dp[i-W-1][j]+j*bp[i];
                while(head<=tail&&newdp>q[tail]) tail-=1;
                tail+=1;
                q[tail]=newdp,J[tail]=j;
                //j[tail]这一步很重要,因为是多重背包,每一次入货出货都有限制,
                //而由于使用单调队列取得的最优解不一定符合限制条件,会过时

                //while(/*J[head]-j<bs[i]*/J[head] - bs[i] > j&&head<=tail) head+=1;  //维护单调队列的长度
                if(head<=tail) dp[i][j]=max(dp[i][j],q[head]-j*bp[i]);
            }

            if(i==n){
                for(int j=0;j<=maxp;j+=1){
                    ans=max(ans,dp[n][j]);
                }
            }
        }

        printf("%d\n",/*dp[n][0]*/ans);
    }
    return 0;
}


 
 
简明版
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <numeric>
#include <functional>
#define maxn 2052

using namespace std;
int ap[maxn],bp[maxn],as[maxn],bs[maxn];
int dp[maxn][maxn];
int q[maxn],j[maxn];
int n,maxp,w,ans;

int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        memset(dp,128,sizeof(dp));
        ans=0;
        scanf("%d %d %d",&n,&maxp,&w);
        for(int i=1;i<=n;i+=1){
            scanf("%d %d %d %d",&ap[i],&bp[i],&as[i],&bs[i]);
        }

        dp[0][0]=0;
        for(int i=1;i<=w+1;i+=1){
            for(int j=0;j<=maxp&&j<=as[i];j+=1){
                dp[i][j]=-j*ap[i];
            }
        }
        for(int i=1;i<=n;i+=1){
            for(int j=0;j<=maxp;j+=1){
                dp[i][j]=max(dp[i][j],dp[i-1][j]);
            }
            if(i<w+2) continue;
            int tail=0,head=1;
            for(int j=0;j<=maxp;j+=1){
                while(j[head] + as[i] < j&&head<=tail) head+=1;
                int newdp=dp[i-w-1][j]+j*ap[i];
                while(head<=tail&&newdp>q[tail]) tail-=1;
                tail+=1;
                q[tail]=newdp,j[tail]=j;
                if(head<=tail) dp[i][j]=max(dp[i][j],q[head]-j*ap[i]);
            }

            tail=0,head=1;
            for(int j=maxp;j>=0;j-=1){
                while(j[head] - bs[i] > j&&head<=tail) head+=1;
                int newdp=dp[i-w-1][j]+j*bp[i];
                while(head<=tail&&newdp>q[tail]) tail-=1;
                tail+=1;
                q[tail]=newdp,j[tail]=j;
                if(head<=tail) dp[i][j]=max(dp[i][j],q[head]-j*bp[i]);
            }

            if(i==n){
                for(int j=0;j<=maxp;j+=1){
                    ans=max(ans,dp[n][j]);
                }
            }
        }

        printf("%d\n",/*dp[n][0]*/ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值