单调队列 POJ2823

看这个问题:An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position.Your task is to determine the maximum and minimum values in the sliding window at each position.

也就是有一个数列a,要求你求数列b和c,b[i]是a[i]…a[i+w-1]中的最小值,c[i]是最大值。如果a是1,3,-1,-3,5,3,6,7,则b为-1,-3,-3,-3,3,3,c为3,3,5,5,6,7。

这个问题相当于一个数据流(数列a)在不断地到来,而数据是不断过期的,相当于我们只能保存有限的数据(sliding window中的数据,此题中就是窗口的宽度w),对于到来的查询(此题中查询是每时刻都有的),我们要返回当前滑动窗口中的最大值\最小值。注意,元素是不断过期的。

解决这个问题可以使用一种叫做单调队列的数据结构,它维护这样一种队列:

a)从队头到队尾,元素在我们所关注的指标下是递减的(严格递减,而不是非递增),比如查询如果每次问的是窗口内的最小值,那么队列中元素从左至右就应该递增,如果每次问的是窗口内的最大值,则应该递减,依此类推。这是为了保证每次查询只需要取队头元素。

b)从队头到队尾,元素对应的时刻(此题中是该元素在数列a中的下标)是递增的,但不要求连续,这是为了保证最左面的元素总是最先过期,且每当有新元素来临的时候一定是插入队尾。

满足以上两点的队列就是单调队列,首先,只有第一个元素的序列一定是单调队列。

那么怎么维护这个单调队列呢?无非是处理插入和查询两个操作。

对于插入,由于性质b,因此来的新元素插入到队列的最后就能维持b)继续成立。但是为了维护a)的成立,即元素在我们关注的指标下递减,从队尾插入新元素的时候可能要删除队尾的一些元素,具体说来就是,找到第一个大于(在所关注指标下)新元素的元素,删除其后所有元素,并将新元素插于其后。因为所有被删除的元素都比新元素要小,而且比新元素要旧,因此在以后的任何查询中都不可能成为答案,所以可以放心删除。

对于查询,由于性质b,因此所有该时刻过期的元素一定都集中在队头,因此利用查询的时机删除队头所有过期的元素,在不含过期元素后,队头得元素就是查询的答案(性质a),将其返回即可。

由于每个元素都进队出队一次,因此摊销复杂度为O(n)。

POJ2823就是上面描述的那道题。

下面是代码,很无聊的是这道题竟然卡scanf和printf,如果用cin,cout就会超时,觉得有点无聊。

   1:  #include <iostream>
   2:  #include <vector>
   3:   
   4:  using namespace std;
   5:   
   6:  struct elem 
   7:  {
   8:      int v;
   9:      int p;
  10:  };
  11:   
  12:  int main()
  13:  {
  14:      int l,w;
  15:      cin>>l>>w;
  16:   
  17:      elem* q1 = new elem[l+w];
  18:      elem* q2 = new elem[l+w];
  19:   
  20:      int num,max;
  21:   
  22:      int head1 = 0,tail1 = 0;
  23:      int head2 = 0,tail2 = 0;
  24:      cin>>num;
  25:      q1[tail1].v = num;
  26:      q1[tail1].p = 0;
  27:   
  28:      q2[tail2].v = num;
  29:      q2[tail2].p = 0;
  30:   
  31:      vector<int> ans1,ans2;
  32:   
  33:      for (int t = 1;t < w;t++)
  34:      {
  35:          scanf("%d",&num);
  36:   
  37:          while (head1 <= tail1 && q1[tail1].v >= num)
  38:              --tail1;
  39:          q1[++tail1].v = num;
  40:          q1[tail1].p = t;
  41:   
  42:          while (head2 <= tail2 && q2[tail2].v <= num)
  43:              --tail2;
  44:          q2[++tail2].v = num;
  45:          q2[tail2].p = t;
  46:      }
  47:   
  48:      ans1.push_back(q1[head1].v);
  49:      ans2.push_back(q2[head2].v);
  50:   
  51:      for (int t = w;t < l;t++)
  52:      {
  53:          scanf("%d",&num);
  54:   
  55:          {
  56:   
  57:              while (head1 <= tail1 && q1[tail1].v >= num)
  58:                  --tail1;
  59:              q1[++tail1].v = num;
  60:              q1[tail1].p = t;
  61:   
  62:              while (t - q1[head1].p >= w)
  63:                  ++head1;
  64:   
  65:              ans1.push_back(q1[head1].v);
  66:          }
  67:   
  68:          {
  69:              while (head2 <= tail2 && q2[tail2].v <= num)
  70:                  --tail2;
  71:              q2[++tail2].v = num;
  72:              q2[tail2].p = t;
  73:   
  74:              while (t - q2[head2].p >= w)
  75:                  ++head2;
  76:   
  77:              ans2.push_back(q2[head2].v);
  78:          }    
  79:      }
  80:   
  81:      delete []q1;
  82:      delete []q2;
  83:   
  84:      for (vector<int>::iterator it = ans1.begin();it != ans1.end();it++)
  85:      {
  86:          printf("%d",*it);
  87:          if (it != ans1.end()-1)
  88:              printf(" ");
  89:          else
  90:              printf("\n");
  91:      }
  92:   
  93:      for (vector<int>::iterator it = ans2.begin();it != ans2.end();it++)
  94:      {
  95:          printf("%d",*it);
  96:          if (it != ans2.end()-1)
  97:              printf(" ");
  98:          else
  99:              printf("\n");
 100:      }
 101:  }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值