為了解決滑動窗口,我們引入單調隊列的概念。
分析題目的要求,我們需要建立一種數據結構,可以滿足以下要求:
-
可以快速讀取一個區間的最大值和最小值
-
能根據編號的大小將元素快速彈出
先分析最大值。對於上述要求,我們可以用一個單調隊列來解決這個問題。
我們不妨先看一組測試數據。
8 3
1 3 -1 -3 5 3 6 7
滑動窗口的運動軌跡如下:
1] 3 -1 -3 5 3 6 7
1 3 ] -1 -3 5 3 6 7
[1 3 -1] -3 5 3 6 7 此時滑動窗口已經完全進入了數列
1 [3 -1 -3] 5 3 6 7
1 3 [-1 -3 5] 3 6 7
1 3 -1 [-3 5 3] 6 7
1 3 -1 -3 [5 3 6] 7
1 3 -1 -3 5 [3 6 7] 滑動窗口已經滑到了最右邊
我們可以用一個單調遞減隊列來解決這個問題——我們可以在隊首取到最大值。
我們用一個變量 \(i\) 來模擬窗口的右側。任意一個時刻內,寬度為\(m\)的窗口,可以表示成一個運動的區間\([i-m+1,i]\)。我們讓\(i\)從1到n循環枚舉,每一次,我們都對掃描到的元素進行判斷,看其能否進入隊列。注意,我們使用的是一個單調隊列,隊列里面的元素是單調遞減的,這樣我們就可以在對頭取到最大值。如果當前元素比隊尾的元素還要大,根據單調隊列的定義,若把當前元素加入到隊列中,那么原來隊尾的元素就會處於一個低谷狀態:它是不可能成為最大值的。原因很簡單:隊列里面的所有元素都會往隊首跑,這個“低谷狀態”的隊列元素最終會到達隊首,而它的前一號元素會比它大。這不符合我們“在隊首取得最大值”的要求。這個“低谷元素”就沒有存在的必要了。
while(head<=tail && q[tail]<=a[i])
--tail;
q[++tail]=a[i];
p[tail]=i;//p表示隊列對應元素的編號
我們發現,這里單調隊列的使用有一點點像“棧”。如果僅僅只是像這樣子掃描,然后入隊,我們還不如建立一個單調棧呢?
其實不然。單調隊列有一個特點,就是既可以從隊首出隊,又可以從隊尾出隊。我們除了考慮快速取得最值,還要考慮一點:由於是滑動窗口,有些窗口內的元素最終會運動到窗口外。所以,我們還要考慮隊列里面的元素是否“過時”。
由於我們是按照時間順序將元素存入隊列內,因此過時的元素更有可能出現在隊首,因為隊尾都是新鮮的元素。由於窗口的右邊界是\(i\),我們只要判斷隊列元素的編號和窗口左邊界的關系\(i-m+1\)就可以了。如果當前元素的編號\(rank<i-m+1\),即\(rank<=i-m\),我們就把它從隊首彈出。
while(p[head]<=i-m)
++head;
綜上所述,我們可以用單調隊列解決這個問題。分析最小值同理。
#include<bits/stdc++.h>
#define For(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;
struct Mq{
static const int nmax=1000001;
int n,k,a[nmax];
int q[nmax],head,tail,p[nmax];
void read()
{
scanf("%d %d",&n,&k);
for(register int i=1;i<=n;++i)
scanf("%d",&a[i]);
}
void Mmax()
{
head=1;
tail=0;
for(register int i=1;i<=n;++i)
{
while(head<=tail && q[tail]<=a[i])
--tail;
q[++tail]=a[i];
p[tail]=i;
while(p[head]<=i-k)
++head;
if(i>=k)printf("%d ",q[head]);
}
printf("\n");
}
void Mmin()
{
head=1,tail=0;
for(register int i=1;i<=n;++i)
{
while(head<=tail && q[tail]>=a[i])
--tail;
q[++tail]=a[i];
p[tail]=i;
while(p[head]<=i-k)
++head;
if(i>=k)
printf("%d ",q[head]);
}
printf("\n");
}
}monotone_queue;
int main()
{
monotone_queue.read();
monotone_queue.Mmin();
monotone_queue.Mmax();
return 0;
}
總結一下單調隊列的三部曲:
- 判單調
- 判過期
- 更答案
注意一下,這三個步驟的具體順序還是要看題目的要求。注意在掃描的過程中,只有當當前新決策的收益或代價可以確定時,才能判定單調,且必須將其插入隊列。
建議根據上面的要求,在以下兩種順序中選一個:
判單調\(\rightarrow\)判過期\(\rightarrow\)更答案
判過期\(\rightarrow\)更答案\(\rightarrow\)判單調
