單調佇列(洛谷p1886)模板題
阿新 • • 發佈:2021-02-14
例題:洛谷p1886滑動視窗
有一個長為 n的序列 a,以及一個大小為 k的視窗。現在這個從左邊開始向右滑動,每次滑動一個單位,求出每次滑動後窗口中的最大值和最小值。
例如:
The array is [1,3,-1,-3,5,3,6,7], and k = 3。
單調佇列有兩個性質
1.佇列中的元素其對應在原來的列表中的順序必須是單調遞增的。
2.佇列中元素的大小必須是單調遞*(增/減/甚至是自定義也可以)
單調佇列與普通佇列不一樣的地方就在於單調佇列既可以從隊首出隊,也可以從隊尾出隊。
分析
以最小值為例:構造一個單調佇列q[i],記h為元素下標,q中存下標。第一次:q={1};第二次:q={1,2}(因為3>1且在範圍內);第三次:q={3}(因為佇列單調遞增,-1最小,所以擠掉1和3,找最小值的時候也不會輪到它們);第四次:q={4}(1已經在外面了。-3<-1果斷擠掉);第五次:q={4,5}(5>-3 可以作為候選人);第六次:q={4,6}(果斷擠掉,有比5合適的候選人);第七次:b={6,7}(4不在範圍內,出列);第八次:b={6,7,8}
每次都輸出佇列中的第一個元素**(因為每次只有滿足待入隊的元素比佇列中隊尾元素更大才可以入隊,否則需要將比它小的所有佇列中的元素全部出隊,再將它入隊,因此佇列始終單調遞增,不可能存在比第一個元素更小的元素)**。
以此維護佇列中元素序號的單調性,得到結果。
只是比較判斷,時間複雜度很小。
相等的值也要擠掉。
最大值同理。
上程式碼:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
int n,k;
int q1[1000001],q2[1000001];
int a[1000001];
int min_deque()//求視窗內最小值
{
int l=1,r=0;//初始化佇列左右下標(l為左,為右),只有這樣初始化,l<=r才可以保證佇列不為空。)
for(int i=1;i<=n;i++)
{
while(l<=r&&q1[l]+k<=i) l++;// q1[l]+k<=i表明隊首元素已經不在視窗內,應將隊首出列
while(l<=r&&a[i]<a[q1[r]]) r-- ;//若待入隊元素比 隊尾元素小,隊尾出隊,直到隊內沒有比它再大的元素為止,保證佇列的單調遞增
q1[++r]=i;//入隊
if(i>=k) printf("%d ",a[q1[l]]);//當i<k時,說明視窗大小沒有達到k,不會有輸出,當i>=k後,每次視窗滑動都應輸出隊首
}
cout<<endl;//不要忘記換行
}
int max_deque()//求視窗內的最大值,步驟類似最小值
{
int l=1,r=0;
for(int i=1;i<=n;i++)
{
while(l<=r&&q2[l]+k<=i) l++;
while(l<=r&&a[i]>a[q2[r]]) r--;
q2[++r]=i;
if(i>=k) printf("%d ",a[q2[l]]);
}
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
min_deque();
max_deque();
return 0;
}//自認為碼風還是比較簡潔的(#^.^#)