单调队列优化DP

昨天在HDU上看到的这题 :  HDU 3401 Trade

题意 : 一个人一开始有很多钱,然后买股票,总共天数是T天,然后如果在i天买进(卖出)一部分股票的话,下一次操作至少要在W+1天后了,然后告诉你这只股票每天的买进和卖出价格,每天最多买进和卖出的股票数有一定的限制,一个人最多拥有的股票是MaxP,问你最后一天最多能够赚多少钱。

思路 : 其实转移方程感觉很裸,令DP[i][j]表示第i天拥有j只股票所能够获得的最大价值,然后DP[i][j] = max(dp[i-1][j],dp[i-W-1][k] + F(买进或者卖出一部分)); 其中DP[i-W-1]k]来源有两部分,一部分是k < j然后在i天时买进(j-k)只,即DP[i-W-1][k] - (j - k) * buy[i] ,另一部分是k > j 在i 天卖出了(k - j)只,即DP[i-W-1][k] + (k - j) * sell[i];

思路很清晰,但是因为朴素的DP的话复杂度在O(Maxp * T * T),这道题是肯定爆的。

后来看到有种叫做单调队列的东西,然后学了下,切了一道水题 : poj 2823 Sliding Window

单调队里做法 : 考虑在i 天买进了一些stocks : DP[i][j] = max(dp[i][j],dp[i-W-1][k] - (j - k) * buy[i]);

将这个状态转移方程变下形 : DP[i][j] = max(dp[i][j],{dp[i-W-1][k] + k * buy[i]} - j * buy[i]);

因为在考虑(i,j)这个状态时j * buy[i] 是不会变的,我们需要的就是找到一个k使得它的P(k) = DP[i-W-1][k] + k * buy[i]最大,也就是简化成了 : 在 k 属于 [j-i天最大能买多少stocks , j)这个范围下的最大P(k),所以我们只要开一个单调队里来维护即可,队首元素最大。同理另一种也是如此。


#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int INF = 9999999;
const int MAXN = 2005;


int n,W,MaxP,buy[MAXN],sell[MAXN],MaxB[MAXN],MaxS[MAXN];
int que[MAXN],tot[MAXN],head,tail;
int dp[MAXN][MAXN];

void init()
{
      for (int i = 0;i <= n;i++)
            for (int j = 0;j <= MaxP;j++)
            dp[i][j] = -INF;
      for (int i = 1;i <= W+1;i++)
            for (int j = 0;j <= min(MaxP,MaxB[i]);j++)
            dp[i][j] = - buy[i] * j;
}

int solve()
{
      init();
      for (int i = 1;i <= n;i++)
      {
            for (int j = 0;j <= MaxP;j++)
                  dp[i][j] = max(dp[i-1][j],dp[i][j]);
            if (i <= W + 1)continue;
            int cur = i - W - 1;

            head = tail = 0;
            for (int k = 0;k <= MaxP;k++)
            {
                  while (head != tail && que[tail] < dp[cur][k] + k * buy[i]) tail--;
                  que[++tail] = dp[cur][k] + k * buy[i]; tot[tail] = k;
                  while (tot[head+1] < k - MaxB[i]) head++;
                  dp[i][k] = max(dp[i][k],que[head+1] - k * buy[i]);
            }

            head = tail = 0;
            for (int k = MaxP;k >= 0;k--)
            {
                  while (head != tail && que[tail] < dp[cur][k] + k * sell[i]) tail--;
                  que[++tail] = dp[cur][k] + k * sell[i]; tot[tail] = k;
                  while (tot[head+1] > k + MaxS[i]) head++;
                  dp[i][k] = max(dp[i][k],que[head+1] - k * sell[i]);
            }
      }
      int ans = -INF;
      for (int i = 0;i <= MaxP;i++)ans = max(ans,dp[n][i]);
      return ans;
}

int main()
{
      int T;
      scanf("%d",&T);
      for (int cas = 1;cas <= T;cas++)
      {
            scanf("%d%d%d",&n,&MaxP,&W);
            for (int i = 1;i <= n;i++)
            {
                  scanf("%d%d%d%d",&buy[i],&sell[i],&MaxB[i],&MaxS[i]);
            }
            printf("%d\n",solve());
      }
      return 0;
}


另外单调队列还可以优化多重背包,不过我觉得多重背包问题二进制优化就已经差不多了。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值