1. 程式人生 > >caioj 1172: 單調佇列(過渡題)

caioj 1172: 單調佇列(過渡題)

1172: [視訊]單調佇列(過渡題)題目
【題意】
給定一個n個數的數列,從左至右輸出每個長度為m的數列段內的最大數。
比如8個數的數列[1 3 -1 -3 5 3 6 7],m=3,那麼每連續3個最大值如下:

位置

最大值

[1  3  -1] -3  5  3  6  7 

3

 1 [3  -1  -3] 5  3  6  7 

3

 1  3 [-1  -3  5] 3  6  7 

5

 1  3  -1 [-3  5  3] 6  7 

5

 1  3  -1  -3 [5  3  6] 7 

6

 1  3  -1  -3  5 [3  6  7]

7

【輸入格式】
第一行兩個整數n和m( 1<= n <= 20 0000,m<=n)。
下來給出n個整數。
【輸出格式】
一行一個整數,表示每連續m個數的最大值。
【樣例輸入】
8 3
1 3 -1 -3 5 3 6 7
【樣例輸出】
3
3
5
5
6
7

思路:這道題絕對是單調佇列裡面最簡單的一道題,只是過渡題,還不算是難的,單調佇列和棧有點像,然後我也不說太多了因為這個在程式碼中寫的超級詳細了

/*單調佇列和棧有點像,但是棧是先進後出;單調佇列是先進先出
同時,單調佇列是成遞減的佇列的,就是第一個也就是隊頭是最大的
隊尾是最小的,然後無數次刪減之後的佇列就是要輸出的答案 
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
    int x,p;//x表示的是這個數的值,p表示的是在單調佇列中的位置 
}list[210000];//記錄當前這個數及前面的值和位置 
int a[210000];//儲存當前這個數 
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    list[1].x=-999999999; list[1].p=0;//初始化,因為要求最大值所以要定義最小的值,第一個的位置為0 
    int head,tail;//隊頭和隊尾 
    head=tail=1;//都要為1,不然就沒有人 
    for(int i=1;i<=n;i++)//尋找最大值 
    {
        //一開始一定要 head<=tail 才表示這一條佇列中有人 
        while(head<=tail && i-list[head].p>=m) head++;
        /*如果包括自己的前10個人不刪掉,其他都要刪掉,就是從第一個開始刪,離自己最遠的開始刪
        也就是說 如果我是 15, 我要求的是 長度為5的佇列, 那麼包括我自己  15 14 13 12 11,這幾個人不該刪,因為這個才是完整的佇列
        而10 也就是 15-m=15-5=10,這個也要刪掉因為包括了自己,所以要 >=m, 除了 15 14 13 12 11不用刪,其他全部刪掉
        而 不是 head-- 而是 head++ 是因為 我們 head++就是增加後面的 tail, 如果是 head--,就要將整個隊伍前移,自然沒那麼方便
        所以我們要用head--,
        總結起來就是 刪隊頭,除了不該刪的 
        */
        while(head<=tail && list[tail].x<=a[i]) tail--;
        /*
        這一步就是刪隊尾,更加複雜,head[tail].x也可以表示為自己,因為是從自己開始往前刪減,所以才能是隊頭最大,隊尾最小
        然後我們從自己的前面開始刪,刪啊刪啊,比自己小的都要刪掉,遇到比自己大的就停止,不進行
        比如說 總共有9個數 我是第6個  需要一個長度為 3 的佇列, 那麼 假設 10 20 15 7 9 10 33 24 17,我是第6個,
        然後我們經過上一步 就把前面的10 20 15 刪掉了,但是他們還在因為我們並沒有刪,只是增加了結尾(上面提到)
        那麼這一步就是從自己開始向前尋找,好的 找到了9,比自己小,刪掉,這一回是真的刪掉,因為這個數到後面就再也沒有用了
        繼續向前看到了7,也比自己小,繼續刪掉,那麼在 7 9 10中 10為最大值,
        這個時候 15就不開心了,因為 刪完 7 9之後, 15 在 10的前面,按常理講,應該是要成為 和10在一起的最大值
        但是要記住,前面刪掉了,如果沒有刪,15是根本用不到的,所以就是要有前面的那一步才能繼續,然後 不能滿足 15
        所以在  7 9 10中的最大值就是 10, 15自然有人處理
        然後處理之後剩下的就是  10 20 15 10 33 24 17,然後一直到所有m佇列刪完之後的佇列就是最終的佇列 
        */
        tail++; list[tail].x=a[i]; list[tail].p=i;
        /*
        因為前面是計算我自己前面的,所以tail++之後的tail,也就是現在的最後一個就是我自己的值
        然後這個最後一個尾巴的位置就是當前迴圈到的i,也就是我自己的位置 
        */
        if(i>=m) printf("%d\n",list[head].x);//找到的數大於等於要求的數之後,就可以直接輸出最終的佇列,
        //因為這個佇列都是最大的佇列,也就是所有佇列的隊頭 
    }
    return 0;
}