NOIP学习笔记(一)——单调队列

单调队列(笔记)

定义:一个队列,其内部元素是按照一定顺序排列的。

         对于一个队列(假设是q),它的每一个元素是按照递增或者是递减的规律排列的。

如:

Q[1]=1;

Q[2]=2;

Q[3]=3;

Q[4]=5;

Q[5]=12;

………………

初次看来,单调队列仿佛与优先队列有些许相似。他们都是将一定量的数据按照一定的顺序进行排列。但是不同的是,单调队列可以对读入的数据进行取舍,选择是否让他加入队列中。而在这个过程中,同时也实现了单调队列内部的一次更新,对内部数据进行了简化与处理。这不是最重要的。最重要的是:单调队列在把数据扫描过一遍以后,就已经得到了答案,时间复杂度是O(n)。而优先队列的排序原理是堆排序,最优秀的时间复杂度才达到O(nlogn)。显然,优先队列在某些问题上并不如单调队列优秀。

扫描二维码关注公众号,回复: 6192720 查看本文章

例题:

https://www.luogu.org/problemnew/show/P1886

         对于一个数列num,有一个可以滑动的‘窗口’沿着他运动。每执行一次操作,它就会向右平移一格。现在请求出遍历一遍后窗口内取过的最大值和最小值。

 

解法一:暴力出奇迹!!!(此处不细说,反正TLE)

解法二(正解):单调队列。

         思路:

对于输入的数组num,我们暂时考虑不进行处理。对于窗口,可以把它看做一个head和tail在变化的队列,然后在所示范围内进行搜索。问题来了,这样和暴力有什么区别?

问题处理的精妙处就在这里。如果我们把窗口里的数字储存下来,然后再进行搜索处理,实际上是做了许多无用功。这时我们不妨考虑,找一个数组,把问题所需要的答案排在最前面,其余的处理掉(因为被排出的数据在本次操作中已经没有机会成为我们想要的答案,下次操作再把它们考虑在内)。这时每一次我们所进行的操作只是一个简单的插排(没用的数据在本次操作中全部去除)。

但是另一个问题来了:对于每一个数据,这样的操作相当于进行了一次排序,那么他原本的顺序不就被打乱了吗?

没关系。为了解决这个问题,我们可以设置一个结构体(或者是数组)(我不喜欢),让它来存储这个数据中每一个数据点的序号。然后再将其放入单调队列里。

然后又会有疑问:每一次窗口都会移动,难道还要判断队列里面的每一个元素是否都在范围之内吗?

其实不用。每一次操作完成后,队列里剩余的元素是上一次所遗留的元素应该是上一次操作中的最大元素以及比目前元素大的元素。所以只需要判断head是否超出范围(因为只有head是答案所需要的解)问题现在就变得比较简单了。只需要在尽量少的便利情况下完成对队列的构建以及判断即可。

void workermax()//单调递减的队列
{
    int head=1,tail=0;
    for(int i=1;i<=n;i++)
    {
        while(head<=tail&&maxn[tail].x<=num[i])
            tail--;
        maxn[++tail].x =num[i];
        maxn[tail].y =i;
        while(head<=tail&&maxn[head].y <=i-k)
            head++;
        if(i>=k)printf("%d ",maxn[head].x );
    }
    printf("\n");
}
void workermin()//单调递增的队列
{
    int head=1,tail=0;
    for(int i=1;i<=n;i++)
    {
        while(head<=tail&&minx[tail].x>=num[i-1])
            tail--;
        minx[++tail].x =num[i-1];
        minx[tail].y =i-1;
        while(head<=tail&&minx[head].y <=i-k)
            head++;
        if(i>=k)printf("%d ",minx[head].x );
    }
    printf("\n");
}

(这个是对于队列构建的核心代码)

我们分析一下:

  1. 首先对于第一个while循环,我们是为了判断队尾元素是否符合我们的要求(即是否要比当前元素大),以此为目的来维护队列的单调性。Tail—相当于将没用的元素放空,节省空间,让后续元素进队。
  2. 其次的赋值语句就是让符合条件的元素入队,并且储存它的顺序。
  3. 第二层while循环,判断head是否还在窗口之内。I-k是窗口的涵盖范围。Head++则是放空队头。
  4. If判别语句,当前循环到的元素是否已经达到了目前窗口的边界。如果到达了,输出答案,窗口移动(具体表现是队列在此之后每一次操作都要输出队头)。

大体分析一下,单调队列适用于在一个已经给定数据范围的数据里面求解最大值或最小值的运算,并且其他值对于后续的运算价值不大。这样,单调队列可以很好地代替优先队列,以更小的代价解决问题。

猜你喜欢

转载自www.cnblogs.com/sisicici/p/10845624.html