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


题目链接:点击打开链接


题目大意:

就是有一个人知道每一天的股票的购入和卖出的价钱(注意只有一种股票),并且每天能够购入和卖出的股票的数量也是有限制的。第 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|1
using 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|1
using 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);
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值