[洛谷P1886]滑動窗口 (單調隊列)(線段樹)

---恢復內容開始---

這是很好的一道題

題目描述:

現在有一堆數字共N個數字(N<=10^6),以及一個大小爲k的窗口。

現在這個從左邊開始向右滑動,每次滑動一個單位,求出每次滑動後窗口中的最大值和最小值。

例如:

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


窗口大小爲3.

則如下圖所示:

 

輸入輸出格式:

輸入格式:


輸入一共有兩行,第一行爲n,k。

 

第二行爲n個數(<INT_MAX).

 

輸出格式:

輸出共兩行,第一行爲每次窗口滑動的最小值

 

輸入樣例:

 

8 3
1 3 -1 -3 5 3 6 7

 

輸出樣例:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

 

 

解決方案:

(一)st表

(二)線段樹

這裏用到了兩個結構體,然後就是進行普通的線段樹求最大最小,這裏就不再贅述了q

第一個結構體是查詢用的

第二個結構體就是線段樹了,這裏我用了一個構造函數;

其實這些操作只是爲了加速我們的線段樹過程(讓它別T)

不過總體地實現還是相對比較優美(複雜)的q

Code:

 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define inf 2147483647
using namespace std;
int a[12345678],n,k;
struct search_tree
{
    int minn;
    int maxn;
}q;
struct Segtree
{
    int minv[12345678],maxv[12345678];
    void pushup(int rt)
    {
        maxv[rt] = max(maxv[rt<<1],maxv[rt<<1|1]);
        minv[rt] = min(minv[rt<<1],minv[rt<<1|1]);
    }
    void build(int rt,int l,int r)
    {
        if(l == r)
        {
            maxv[rt] = a[l];
            minv[rt] = a[l];
            return ;
        }
        int mid = (l + r)>>1;
        build(rt<<1,l,mid);
        build(rt<<1|1,mid+1,r);
        pushup(rt);
    }
    search_tree solve(int rt,int l,int r,int ll,int rr)  //ll rr 爲待求量 
    {
        if(ll <= l && rr >= r)
        return (search_tree)
        {
            minv[rt],
            maxv[rt]
        };
        int mid = (l+r)>>1;
        int minn = inf , maxn = -inf;
        search_tree ans;
        if(ll <= mid)
        {
            ans = solve(rt<<1,l,mid,ll,rr);
            maxn = max(maxn,ans.maxn);
            minn = min(minn,ans.minn);
        }
        if(rr > mid)
        {
            ans = solve(rt<<1|1,mid+1,r,ll,rr);
            maxn = max(maxn,ans.maxn);
            minn = min(minn,ans.minn);
        }
        return (search_tree)
        {
            minn,
            maxn
        };
    }
}segtree;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    segtree.build(1,1,n);
    for(int i=1;i<=n - k + 1;i++)
    {
        q = segtree.solve(1,1,n,i,i + k - 1);
        printf("%d ",q.minn);
        a[i] = q.maxn;
    }
    printf("\n");
    for(int i=1;i<=n-k+1;i++)
    printf("%d ",a[i]);
    return 0;
}

 

 

 

 

 

(三)單調隊列

單調隊列概念:

  1. 隊列中的元素其對應在原來的列表中的順序必須是單調遞增的。

  2. 隊列中元素的大小必須是單調遞*(增/減/甚至是自定義也可以)

這保證了單調隊列的雙有序

但是單調隊列有一個特殊的特點就是可以雙向操作出隊。

但是我們並不是把單調隊列裏的每一個數都要存一遍,我們只需要存儲這些單調隊列裏有序環境中有序的數(即我們所要求的目的)

這個概念還是很抽象的q

不過從這個題來看還是可以有所幫助的q

並不是每一個數的記錄都是有意義的;

我們只需要存儲那些對於我們來說有意義的數值;

以此題求最小值爲栗子:

若有ai和aj兩個數,且滿足i<j。

如果ai>aj,那麼兩個數都應該記錄;

但是如果aiaj,那麼當aj進入區間後,ai的記錄就沒有意義了。

我們假設每個數能作爲區間最大值的次數(即它可以存在區間內的次數)爲它的貢獻,當aj進入區間以後,在區間中存在的時間一定比ai長,也就說明ai一定不會再做貢獻了

我們確定沒有貢獻的點,便可以直接刪去

Code:

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define MAXN 1008666 
using namespace std;
struct Node
{
    int v;
    int pos;
}node[MAXN << 1];
int n,a[MAXN << 1],h = 1,t,k;
int m;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)  //維護單調遞減隊列 
    {
        while(h <= t && node[h].pos + k <= i)
        h++;
        while(h <= t && node[t].v >= a[i])
        t--;
        node[++t].v = a[i];
        node[t].pos = i;
        if(i >= k)
        printf("%d ",node[h].v);
    }
    h = 1;
    t = 0;
    printf("\n");
    for(int i=1;i<=n;i++)  //維護單調遞增隊列 
    {
        while(h <= t && node[h].pos + k <= i)
        h++;
        while(h <= t && node[t].v <= a[i])
        t--;
        node[++t].v = a[i];
        node[t].pos = i;
        if(i >= k)
        printf("%d ",node[h].v);
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章