1. 程式人生 > >POJ 2823 (從經典滑動視窗最大值問題入門單調佇列)

POJ 2823 (從經典滑動視窗最大值問題入門單調佇列)

題目連結

題目大意

輸入一個長度為n(n106)的數列,給定一個長度為k的視窗,讓這個視窗在數列上移動,求移動到每個位置視窗中包含數的最大值和最小值。即設序列為A1,A2,…,An,設f(i)=min{Aik+1,Aik+2,…,Ak} ,g(i)=max{Aik+1,Aik+2,…,Ak}
求:f(k),f(k+1),…,f(n) g(k),g(k+1),…,g(n).

分析

本題算是單調佇列的經典入門題。因此拿來學習單調佇列。
如果單純對每一個i都去掃描前面k個數去求最值,那複雜度為o(n*k),肯定超時。
如果用線段樹或者樹狀陣列維護區間最值,那複雜度為o(nlogn)。
但還有更簡潔更高效的辦法,即單調佇列

。//可參考紫書P241
單調佇列是一種特殊的佇列,它能在佇列兩端進行刪除操作,並始終維護佇列保持一種單調性。
以求視窗最小值為例,我們需要維護一個內部元素值遞減的佇列來模擬滑動視窗,使每一次查詢取隊首元素即為答案。如何維護呢?有以下兩種操作:

1.從隊尾插入元素:當有新元素需要入隊時,讓它與當前隊尾元素進行比較,若它小於等於當前隊尾元素(即破壞了原佇列的單調性),那麼刪除隊尾元素,並繼續比較隊尾與新元素,直到找到一個隊尾大於新元素時,將新元素插入到隊尾。被刪除的元素既比新元素大,又會比新元素先滑出視窗,因此肯定不會成為答案。這個操作不斷維護了佇列中的最值。

2.刪除隊首元素:由於序列中的元素當且僅當在滑動視窗時有效,因此,當隊首元素不在滑動視窗內時,就刪除隊首元素。這個操作維護了資料的臨時性。

元素是否過時與其下標有關,因此我佇列中儲存的是序列數對應的下標,這樣每當要入隊一個數,只需訪問隊首元素所存下標在序列中對應的數即可得到答案。
這樣做盡管插入元素時有時需刪除多個數,但每個數最多被刪除一次,因此時間複雜度為O(n).

程式碼

陣列模擬單調佇列

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void
get_min() { int head,tail,i; memset(Q,0,sizeof(Q)); head=1;tail=1; Q[tail]=1; Min[1]=a[1]; for (i=2;i<=n;i++) { while ((head<=tail)&&(a[i]<a[Q[tail]]))//刪除隊尾元素 tail--; //插入隊尾元素 Q[++tail]=i; while ((head<=tail)&&(Q[head]<i-k+1)) //刪除隊首過時元素 head++; /*由於每一層迴圈只新增一個數,因此每次最多刪除一次隊首, 故可改成if語句*/ Min[i]=a[Q[head]]; //訪問答案 } } void get_max() { int head,tail,i; memset(Q,sizeof(Q),0); head=1;tail=1; Q[tail]=1; Max[1]=a[1]; for (i=2;i<=n;i++) { while ((head<=tail)&&(a[i]>a[Q[tail]])) tail--; Q[++tail]=i; while ((head<=tail)&&(Q[head]<i-k+1)) head++; Max[i]=a[Q[head]]; } } int main() { int i; scanf("%d%d",&n,&k); for (i=1;i<=n;i++) scanf("%d",&a[i]); get_min(); get_max(); for (i=k;i<n;i++) printf("%d ",Min[i]); printf("%d\n",Min[n]); for (i=k;i<n;i++) printf("%d ",Max[i]); printf("%d\n",Max[n]); return 0; }

使用STL中的雙端佇列deque容器

#include <iostream>
#include <cstdio>
#include <cstring>
#include <deque>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void Get_Min()
{
    deque<int> Q;
    int i;
    Q.push_back(1);
    Min[1]=a[1];
    for (i=2;i<=n;i++)
    {
        while ((!Q.empty())&&(a[i]<a[Q.back()]))
            Q.pop_back();
        Q.push_back(i);
        while ((!Q.empty())&&(Q.front()<i-k+1))
            Q.pop_front();
        Min[i]=a[Q.front()];
    }
}
void Get_Max()
{
    deque<int> Q;
    int i;
    Q.push_back(1);
    Max[1]=a[1];
    for (i=2;i<=n;i++)
    {
        while ((!Q.empty())&&(a[i]>a[Q.back()]))
            Q.pop_back();
        Q.push_back(i);
        while ((!Q.empty())&&(Q.front()<i-k+1))
            Q.pop_front();
        Max[i]=a[Q.front()];
    }
}
int main()
{
    int i;
    scanf("%d%d",&n,&k);
    for (i=1;i<=n;i++)
        scanf("%d",&a[i]);
    Get_Min();
    Get_Max();

    for (i=k;i<n;i++)
        printf("%d ",Min[i]);
    printf("%d\n",Min[n]);
    for (i=k;i<n;i++)
        printf("%d ",Max[i]);
    printf("%d\n",Max[n]);
    return 0;
}