poj2823 單調隊列(含單調隊列的學習)


  轉自:http://www.sunhongfeng.com/2011/07/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97-poj2823/

  他的分析非常到位,順便把單調隊列給學了。很好,所以轉了他的這篇文章。程序是我后來理解之后自己寫的。

看這個問題:An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves rightwards by one position.Your task is to determine the maximum and minimum values in the sliding window at each position.

也就是有一個數列a,要求你求數列b和c,b[i]是a[i]…a[i+w-1]中的最小值,c[i]是最大值。如果a是1,3,-1,-3,5,3,6,7,則b為-1,-3,-3,-3,3,3,c為3,3,5,5,6,7。

這個問題相當於一個數據流(數列a)在不斷地到來,而數據是不斷過期的,相當於我們只能保存有限的數據(sliding window中的數據,此題中就是窗口的寬度w),對於到來的查詢(此題中查詢是每時刻都有的),我們要返回當前滑動窗口中的最大值\最小值。注意,元素是不斷過期的。

解決這個問題可以使用一種叫做單調隊列的數據結構,它維護這樣一種隊列:

a)從隊頭到隊尾,元素在我們所關注的指標下是遞減的(嚴格遞減,而不是非遞增),比如查詢如果每次問的是窗口內的最小值,那么隊列中元素從左至右就應該遞增,如果每次問的是窗口內的最大值,則應該遞減,依此類推。這是為了保證每次查詢只需要取隊頭元素。

b)從隊頭到隊尾,元素對應的時刻(此題中是該元素在數列a中的下標)是遞增的,但不要求連續,這是為了保證最左面的元素總是最先過期,且每當有新元素來臨的時候一定是插入隊尾。

滿足以上兩點的隊列就是單調隊列,首先,只有第一個元素的序列一定是單調隊列。

那么怎么維護這個單調隊列呢?無非是處理插入和查詢兩個操作。

對於插入,由於性質b,因此來的新元素插入到隊列的最后就能維持b)繼續成立。但是為了維護a)的成立,即元素在我們關注的指標下遞減,從隊尾插入新元素的時候可能要刪除隊尾的一些元素,具體說來就是,找到第一個大於(在所關注指標下)新元素的元素,刪除其后所有元素,並將新元素插於其后。因為所有被刪除的元素都比新元素要小,而且比新元素要舊,因此在以后的任何查詢中都不可能成為答案,所以可以放心刪除。

對於查詢,由於性質b,因此所有該時刻過期的元素一定都集中在隊頭,因此利用查詢的時機刪除隊頭所有過期的元素,在不含過期元素后,隊頭得元素就是查詢的答案(性質a),將其返回即可。

由於每個元素都進隊出隊一次,因此攤銷復雜度為O(n)。

POJ2823就是上面描述的那道題。

我的程序:

 

#include <iostream>
#include <fstream>

using namespace std;

#define MAX 1000001
int A[MAX]; //存儲數據
int Q[MAX]; //隊列
int P[MAX]; //存儲A[i]中的下標i
int Min[MAX];  //輸出最小
int Max[MAX];  //輸出最大
int n,k;

void get_min()
{
    int i;
    int head=1,tail=0;
    for(i=0; i<k-1; i++)  //先把前兩個入隊
    {
        while(head<=tail && Q[tail]>=A[i])  //隊尾元素大於要插入的數
            --tail;
        Q[++tail]=A[i];
        P[tail]=i;
    }

    for(; i<n; i++)
    {
        while(head<=tail && Q[tail]>=A[i])
            --tail;
        Q[++tail]=A[i];
        P[tail]=i;
        while(P[head]<i-k+1)  //判斷數是否過時,即窗口是否已經划過這個數,我這是從0開始計數的。
        {
            head++;
        }
        Min[i-k+1]=Q[head];
    }
}

void get_max()
{
    int i;
    int head=1,tail=0;
    for(i=0; i<k-1; i++)
    {
        while(head<=tail && Q[tail]<=A[i])   //隊尾元素小於要插入的值
            --tail;
        Q[++tail]=A[i];
        P[tail]=i;
    }

    for(; i<n; i++)
    {
        
        while(head<=tail && Q[tail]<=A[i])   //隊尾元素小於要插入的值
            --tail;
        Q[++tail]=A[i];
        P[tail]=i;
        while(P[head]<i-k+1)
            {
            head++;
        }
        Max[i-k+1]=Q[head];
    }
}

void output()
{
       int i;      
      //輸出最下值 
       for(i=0; i<n-k+1; i++)
       {
           if(i==0)
               printf("%d",Min[i]);
           else
               printf(" %d",Min[i]);
       }
       printf("\n");
       //輸出最大值
       for(i=0; i<n-k+1; i++)
       {
           if(i==0)
               printf("%d",Max[i]);
           else
               printf(" %d",Max[i]);
       }
       printf("\n");
}


int main()
{
    int i;
    freopen("acm.txt","r",stdin);
    scanf("%d%d",&n,&k);
    for(i=0; i<n; i++)
    {
        scanf("%d",&A[i]);
    }
    get_min();
    get_max();
    output();
    return 0;
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM