1. 程式人生 > >單調隊列——從入門到入門

單調隊列——從入門到入門

clas pre 使用 ron \n for pro stream ios

單調隊列簡介:

單調隊列其實就是一個裏面元素都單調的雙端隊列。就是這樣的一個東西,我們卻可以用來解決這樣的問題:如果我們有一個數組$ 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\n");
        return 0;
    }

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

    return 0;
}

單調隊列——從入門到入門