單調隊列優化&&P1886 滑動窗口題解

單調隊列:算法

顧名思義,就是隊列中元素是單調的(單增或者單減)。數據結構

在某些問題中可以優化複雜度。優化

在dp問題中,有一個專題動態規劃的單調隊列優化,之後會更新(如今仍是太菜了不會)。spa

在你看到相似於滑動定長度區間的相似問題時能夠考慮單調隊列優化。3d

就像這道題:P1886 滑動窗口。code

 

一道模板題。那麼咱們從題目入手講解。blog

 


首先,你看完了題。。(??)隊列

1.暴力get

樸素的入門級想法就是雙重for循環枚舉當前區間裏的每個元素並取min和max。io

由於外層for枚舉區間頭,內層for枚舉區間內位置,因此複雜度O(外層區間長度*內層區間長度)也就是O(nk)。這題是1e6的數據啊。。T飛了。。

2.st表

學過較爲高級的數據結構的同窗可能會想到線段樹和st表。

其中,線段樹能夠O(nlogn)查詢,O(nlogn)修改。st表只支持查詢,可是能夠O(nlogn)預處理,在這個題中,沒有修改的操做,因此咱們可使用st表,相對於線段樹更優。

可是計算一下,實際複雜度在兩千萬?。。可能過,可是我沒敢試。。。qwq。通常上千萬的複雜度都得看臉過了。。

因此還有更穩定的算法:

3.單調隊列

複雜度O(n),爲線性算法。

主角來啦!

咱們定義一個雙端隊列q,它兩端均可以任意縮短增長長度,咱們嘗試維護它的長度爲k。

當這個窗口向右移動一格時,天然的,要進隊一個元素,出隊一個元素。

咱們嘗試保證出隊的那個元素爲當前區間的最大(舉例子)值,那麼爲了維護隊列的單調性,咱們必須使得隊頭的元素要比後面的元素都大。

考慮這樣一個oi俗語:有些人比你小,他還比你強,那麼你就能夠退役了(深有同感)!

對於當前要進隊的啊a[i],若是你設一個j,保證a[j]在隊中切a[j]比a[i]先入隊(head<j<=tail),若是a[j]比當前a[i]小(比你強大),它還先進隊(比你小),是否是就抹殺掉了a[j]當最大值的機會?

那麼a[j]以及那些像a[j]一類的元素(人)均可以被彈出隊列(集體退役)了。。

爲何寫着寫着莫名辛酸。。去世吧

這也是一個重要的基礎,你必定要想懂這個道理!

因而他轉化成了這樣一句代碼:

彈出隊尾。

那麼,每次只要輸出隊頭(班裏的rk1)就能夠了。

代碼以下:

#include<cstdio>
using namespace std;
int read()
{
    char ch=getchar(),last=' ';
    int ans=0;
    while(ch>'9'||ch<'0')last=ch,ch=getchar();
    while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
    return last=='-'?-ans:ans;
}

int n,a[1000001],k,q[1000001];

inline int minnn()//求最小值
{
    int hea=0,tai=1;
    for(int i=1;i<=n;i++)
    {
        while(hea<=tai&&q[hea]+k<=i)hea++;
        while(hea<=tai&&a[i]<a[q[tai]])tai--;//又小又強
        q[++tai]=i;
        if(i>=k)printf("%d ",a[q[hea]]);//輸出rk1
    }
    printf("\n");
}

inline int maxnn()//求最大值
{
    int hea=0,tai=1;
    for(int i=1;i<=n;i++)
    {
        while(hea<=tai&&q[hea]+k<=i)hea++;
        while(hea<=tai&&a[i]>a[q[tai]])tai--;//又小又強
        q[++tai]=i;
        if(i>=k)printf("%d ",a[q[hea]]);//輸出rk1
    }
    printf("\n");
}

int main(){
    n=read(),k=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
    }
    minnn();
    maxnn();
    return 0;
}

一篇辛酸的講解。

完結。

 

但願本身不要退役

相關文章
相關標籤/搜索