动态规划(DP) 之优先队列优化 HDOJ 3401Trade

HDOJ 3401题目连接

 

 

告诉你 T 天里,物品的买卖价格,和当天最多买卖数量,还有 T 天里最多保存物品数量,每一次买卖间隔时间必须大于 w+1

 

很容易想到DP【i】【j】表示,第i天结束的时候还保存有j个物品时的最大获利

 

初始化dp【i】【j】 = dp【i-1】【j】;

那么状态方程dp【i】【j】 = MAX(dp【k】【j】),其中k<= i - w - 1;

且有                 dp【i】【j】=MAX(dp【k】【z】 - (j - z) * buyValue【i】),其中,k<=i-w-1,z<j且 z>=j-maxBuy【i】;

还有                 dp【i】【j】=MAX(dp【k】【z】 + (z - j) * sellValue【i】),其中,k<=i-w-1,z>j且 z<=j  +  maxSell【i】;

 

所以会有i,j,z三层循环,而他们的范围都是2000,所以有2000^3的复杂度,肯定超时

 

这个时候我们可以用优先队列来优化,使之少一层循环

 

z<j且 z>=j-maxBuy【i】的时候,dp【i】【j】= dp【k】【z】 - (j - z) * buyValue【i】= dp【k】【z】 + z  * buyValue【i】 -  j*buyValue【i】

 

可以发现dp【k】【z】 + z  * buyValue【i】部分只与z有关;

- j*buyValue【i】而后面的部分只与j有关,所有z都一样;

 

所以我们需要的是式子前面这一部分的值越大越好,而z的范围又随着j的变化而变化,刚好是优先队列的使用情况

 

 

这个题目有注意的地方

(1)就是k可以定为i-w-1,因为i-w-1之前的所有并没有比i-w-1优,相反i-w-1这一天有可能比前面的优

(2)别忘了i可以从i-1天来

(3)最后的答案其实一定是剩余0个物品的时候

 

我的代码:

#include<stdio.h>
//#include<string.h>
//#include<algorithm>
#define CANNOT (-0x3fffffff)

#define MAX 2100

int dp[MAX][MAX];
int days[MAX][4];

int que[MAX], tail, front;
int where[MAX];

int t, maxp, w;

int Max(int a, int b)
{
 return a > b ? a : b;
}

int main()
{
 int i, j;
 int cases;
 int result;
 int xx;
 scanf("%d", &cases);
 while(cases --)
 {
  result = 0;
  scanf("%d%d%d", &t, &maxp, &w);
  for(i = 1; i <= t; i ++)
  {
   scanf("%d%d%d%d", &days[i][0], &days[i][1], &days[i][2], &days[i][3]);
  }
  for(i = 1; i <= t; i ++)
  {
   if(i <= w + 1)
   {
    for(j = 0; j <= maxp; j ++)
    {
     if(j <= days[i][2])
     {
      dp[i][j] = -j * days[i][0];
     }
     else
     {
      dp[i][j] = CANNOT;
     }
     if(i > 1)
      dp[i][j] = Max(dp[i][j], dp[i - 1][j]);
    }
   }
   else
   {
    for(j = 0; j <= maxp; j ++)
    { 
     dp[i][j] = dp[i - w - 1][j];
     dp[i][j] = Max(dp[i][j], dp[i - 1][j]);
    }
    tail = -1;
    front = 0;
    int temp = 0;
    for(j = 0; j <= maxp; j ++)
    {
     while(temp < j)
     {
      if(dp[i - w - 1][temp] == CANNOT)
      {
       temp ++;
       continue;
      }
      xx = dp[i - w - 1][temp] + temp * days[i][0];
      while(front <= tail)
      {
       if(xx >= que[tail])
        tail --;
       else
        break;
      }
      que[++ tail] = xx;
      where[tail] = temp;
      temp ++;
     }
     while(front <= tail && j - where[front] > days[i][2])
     {
      front ++;
     }
     if(front <= tail)
     dp[i][j] = Max(dp[i][j], que[front] - j * days[i][0]);
    }

    tail = -1;
    front = 0;
    temp = 0;
    for(j = 0; j <= maxp; j ++)
    {
     while(temp <= j + days[i][3] && temp <= maxp)
     {
      if(dp[i - w - 1][temp] == CANNOT || temp <= j)
      {
       temp ++;
       continue;
      }
      xx = dp[i - w - 1][temp] + temp * days[i][1];
      while(front <= tail)
      {
       if(xx >= que[tail])
        tail --;
       else
        break;
      }
      que[++ tail] = xx;
      where[tail] = temp;
      temp ++;
     }
     while(front <= tail && where[front] <= j)
     {
      front ++;
     }
     if(front <= tail)
     dp[i][j] = Max(dp[i][j], que[front] - j * days[i][1]);
    }
   }
  }
 // result = 0;
 // for(i = 0; i <= maxp; i ++)
 //  result = Max(result, dp[t][i]);
 // printf("%d\n", result);
  printf("%d\n", dp[t][0]);
 }
 return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值