HDU-3401:Trade(dp+单调队列优化)

来源:互联网 发布:李氏筋骨散淘宝 编辑:程序博客网 时间:2024/05/18 02:16


题目链接:点击打开链接


题目大意:

就是有一个人知道每一天的股票的购入和卖出的价钱(注意只有一种股票),并且每天能够购入和卖出的股票的数量也是有限制的。第 i 天买的股票也只能第 i+w+1 天卖出。但是无论何时,他手中持有的股票的数量不能超过m、问他本金无限的情况下最多盈利多少。


题意解析:

很容易分析出来是一道dp题目 每一天的状态可以由他 i-w-1 天前的状态推出来,再枚举他今天 买入 或 卖出 多少股票。dp转移方程就不说了。来说一下这道题需要用到的一个技巧,单调队列优化 dp 。


单调队列优化dp,刚开始着实理解不来 。看大佬代码一头雾水。遂决定先去写个超时代码。果然超时代码写出来之后对比着就比较容易理解了。

单调队列dp 适用于  dp[i]=max/min (f[k]) + g[i]  (k<i && g[i]是与k无关的变量)(借用一下大佬的)这种情况。


接下来谈我自己对单调队列的理解,当然不一定正确,自己瞎猜的。因为 k 是与 i 有关的,理论上来说 我们若想得出正确答案,最暴力的方法是枚举完 i 再枚举 k 。这样的话就是一个双重循环。但是单调队列可以帮助我们一重循环解决。就是直接将 i 枚举一遍。枚举 i 的同时也同时将 k 作为 i 枚举。但是因为 k 和 i 之间有某种关系 。所以我们需要将枚举出来的 i 先保存起来,维护一个单调队列。那么对于你枚举到后面的 i 的时候,这个单调队列里面的头结点对应的 i 一定是最优解并且他将作为 k 参加dp转移方程。这样就可以一重循环完美的解决问题了。不得不说真的神奇。


下面贴两段代码,一个超时,一个是优化过的。对比着看的话应该更容易理解。


/* 优化版 */#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <cmath>#include <vector>#include <queue>#include <map>#include <algorithm>#include <set>#include <functional>#define lson rt<<1#define rson rt<<1|1using namespace std;typedef long long ll;const int INF = 1e9 + 5;int n,m,w,ans;struct node{    int idx;    int kk;};int ap[2005],bp[2005];int as[2005],bs[2005];int dp[2005][2005];node q[2005];   //单调队列int main(){    int QAQ;    scanf("%d",&QAQ);    while(QAQ--)    {        scanf("%d%d%d",&n,&m,&w);        for(int i=1;i<=n;i++)            scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);        for(int i=0;i<=n;i++)            for(int j=0;j<=m;j++)            dp[i][j]=-INF;        for(int i=1;i<=w+1;i++)        //预处理前w+1天的情况            for(int j=0;j<=min(as[i],m);j++)            dp[i][j]=-j*ap[i];        dp[0][0]=0;        int head=0,tail=0;        for(int i=1;i<=n;i++)        {            for(int j=0;j<=m;j++)                dp[i][j]=max(dp[i][j],dp[i-1][j]);  //不买不卖的情况            if(i<=w+1)                continue ;            int g=i-w-1,gg;            head=0;tail=0;            for(int j=0;j<=m;j++)   //买入情况 枚举 j             {                gg=dp[g][j]+j*ap[i];                while(head<tail&&q[tail-1].kk<gg)   // 将j不断加入单调队列并维护                    tail--;                q[tail].idx=j;q[tail++].kk=gg;                while(head<tail&&q[head].idx+as[i]<j)   //将前面部分不符合要求的点去掉                    head++;                dp[i][j]=max(dp[i][j],q[head].kk-j*ap[i]);      //当前最优状态转移方程            }            head=0;tail=0;            for(int j=m;j>=0;j--)   //卖出 同上            {                gg=dp[g][j]+j*bp[i];                while(head<tail&&q[tail-1].kk<gg)                    tail--;                q[tail].idx=j;q[tail++].kk=gg;                while(head<tail&&q[head].idx-bs[i]>j)                    head++;                dp[i][j]=max(dp[i][j],q[head].kk-j*bp[i]);            }        }        ans=0;        for(int i=0;i<=m;i++)            ans=max(ans,dp[n][i]);        printf("%d\n",ans);    }}


/* 未优化版 */#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <cmath>#include <vector>#include <queue>#include <map>#include <algorithm>#include <set>#include <functional>#define lson rt<<1#define rson rt<<1|1using namespace std;typedef long long ll;const int INF = 1e9 + 5;int n,m,w,ans;int ap[2005],bp[2005];int as[2005],bs[2005];int dp[2005][2005];int q[2005];int main(){    int QAQ;    scanf("%d",&QAQ);    while(QAQ--)    {        scanf("%d%d%d",&n,&m,&w);        for(int i=1;i<=n;i++)            scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);        for(int i=0;i<=n;i++)            for(int j=0;j<=m;j++)            dp[i][j]=-INF;        for(int i=1;i<=w+1;i++)        {            for(int j=0;j<=min(as[i],m);j++)                dp[i][j]=-j*ap[i];        }        dp[0][0]=0;        for(int i=1;i<=n;i++)        {            for(int j=0;j<=m;j++)                dp[i][j]=max(dp[i][j],dp[i-1][j]);            if(i<=w+1)                continue ;            int g=i-w-1;            for(int j=m;j>=0;j--)                for(int k=0;k+as[i]<j;k++)      //重点看k的范围 单调队列要用到                    dp[i][j]=max(dp[g][k]-(j-k)*ap[i],dp[i][j]);            for(int j=0;j<=m;j++)                for(int k=min(j+bs[i],m);k>j;k--)       //同上                    dp[i][j]=max(dp[g][k]+(k-j)*bp[i],dp[i][j]);        }        ans=0;        for(int i=0;i<=m;i++)            ans=max(ans,dp[n][i]);        printf("%d\n",ans);    }}




原创粉丝点击