---恢復內容開始---
這是很好的一道題
題目描述:
現在有一堆數字共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; }
(三)單調隊列
單調隊列概念:
-
隊列中的元素其對應在原來的列表中的順序必須是單調遞增的。
-
隊列中元素的大小必須是單調遞*(增/減/甚至是自定義也可以)
這保證了單調隊列的雙有序
但是單調隊列有一個特殊的特點就是可以雙向操作出隊。
但是我們並不是把單調隊列裏的每一個數都要存一遍,我們只需要存儲這些單調隊列裏有序環境中有序的數(即我們所要求的目的)
這個概念還是很抽象的q
不過從這個題來看還是可以有所幫助的q
並不是每一個數的記錄都是有意義的;
我們只需要存儲那些對於我們來說有意義的數值;
以此題求最小值爲栗子:
若有ai和aj兩個數,且滿足i<j。
如果ai>aj,那麼兩個數都應該記錄;
但是如果ai≤aj,那麼當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; }