单调队列——从入门到入门

Posted lixiao189

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单调队列——从入门到入门相关的知识,希望对你有一定的参考价值。

单调队列简介:

单调队列其实就是一个里面元素都单调的双端队列。就是这样的一个东西,我们却可以用来解决这样的问题:如果我们有一个数组$ A $,对于任意的 $ i $ , 求其中 $ i $ 满足 $ 1 le i le n-m+1 $ 中 $ A_i sim A_{i+m-1} $ 中的最小值/最大值 。

单调队列的维护:

单调队列本质上就是一个双端队列,对于双端队列,我们其实可以直接使用C++ STL里的 deeue。关于deeue的用法,大家可以自行上网搜索,这里就不讲了。(主要是我码字太累了)。假设我们要维护一个单调递增的双端队列,也就是我们要维护一个里面元素从第一个到最后一个逐渐增大的双端队列,而且这个双端队列里的元素到当前元素的距离不能大于 $ m $(注意我们是一边维护单调队列一边解决问题的)。如果要维护这样的一个东西,我们首先要先检查队尾的元素,如果有元素大于当前元素,那么当前元素从后面push进去的时候肯定会破坏这个单调队列的单调性,所以我们要把这个单调队列的队尾元素进行pop操作,直到如果我们把当前元素push进单调队列里面的时候不会破坏单调性。但是由于我们随着要插入这个队列里的元素的下标不断增加,如果不处理单调队列里的元素的话,开始push进这个队列的元素到当前元素的距离一定会大于 $ m $ (距离就是两者下标相减后加1的值),所以我们还要对队头进行处理,我们要将队头的东西也要进行pop操作,直到队头的元素到当前元素的距离小于$ m $。这样我们就得到了一个这样的双端队列:如果这个双端队列非空,里面的元素呈现单调递增,且都小于等于当前元素,而且里面元素到当前元素的距离都不大于 $ m $ ,这样当前元素到前 $ m $ 个元素里面的最小值就是队头元素,如果这个队列现在空了,说明前面的元素都不满足限制,那么当前元素前 $ m $ 个元素里的最小值就是当前元素自己。之后我们就可以放心大胆地把这个元素push进队尾了。我们对于每一个待push的元素重复上面的操作,就可以解决问题了。

一个例题:

题目来源:

Luogu [P4392]

题目思路:

这个题目可以看成是单调队列的模板题了,我们只需要用双端队列求出每一个元素及后 $ m-1 $ 个元素里的最大值与最小值就可以了。

代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 1e6+5;

int n,m,c;
int arr[N];
vector <int> ans;  //用来储存答案
deque <int> t1,t2; //单调队列,为了方便计算存的是编号
//t1维护的是一个上升的单调队列,t2维护的是一个下降的单调队列

int main()
{
    scanf("%d %d %d",& n,& m,& c);
    for(int i=1;i<=n;i++)
        scanf("%d",& arr[i]);

    for(int i=1;i<=n;i++)
    {
        if(i==1)
        {
            t1.push_back(i); 
            t2.push_back(i);
            if(m==1 && c==0) //特殊情况
                ans.push_back(i);
        }
        else
        {
            while(arr[t1.back()]>arr[i]) //维护单调
            {
                t1.pop_back();
                if(t1.size()==0)
                    break;
            }
            while(arr[t2.back()]<arr[i])
            {
                t2.pop_back();
                if(t2.size()==0)
                    break;
            }

            //维护区间的长度
            while(t1.size()!=0 && (i-t1.front()+1)>m)
                t1.pop_front();
            while(t2.size()!=0 && (i-t2.front()+1)>m)
                t2.pop_front();

            int min,max;

            if(t1.size()==0)
                min=arr[i];
            else
                min=arr[t1.front()];

            if(t2.size()==0)
                max=arr[i];
            else
                max=arr[t2.front()];

            if((max-min)<=c && max>=min && ((i-m+1)>=1))
                ans.push_back(i-m+1);

            t1.push_back(i);
            t2.push_back(i);
        }
    }

    if(ans.size()==0)
    {
        printf("NONE
");
        return 0;
    }

    vector <int> :: iterator it;
    for(it=ans.begin();it!=ans.end();it++)
        printf("%d
",(*it));

    return 0;
}

以上是关于单调队列——从入门到入门的主要内容,如果未能解决你的问题,请参考以下文章

滑动窗口单调队列入门题

单调栈&单调队列入门

单调队列入门

单调栈&单调队列入门

单调队列入门

单调队列/单调栈入门详解+题目推荐