【学习笔记】单调队列
建议结合板题食用。
单调队列,顾名思义,就是队内元素满足单调性的队列,可以用于在线性的时间复杂度内求一个序列所有长度为 \(k\) 的区间的最值。
先看一下这个序列:
1 14 5 14 19 1 98 10
设这里的 \(k = 3\),我们先维护一个队列:
首先,我们将 \(1\) 和 \(14\) 入队,此时的队列如下:
1 14
然后我们准备将 \(5\) 入队。此时我们可以发现:\(14\) 不可能成为后面区间的最小值。
分类讨论一下:
当一个区间包含 \(14\) 时,那么这个区间也肯定包含 \(5\)(因为 \(5\) 在后面),\(5\) 肯定比 \(14\) 更优。
当一个区间不包含 \(5\) 时,那么这个区间也不会包含这个 \(14\)。
综上,\(14\) 无论如何都不会成为后面区间的最小值,所以我们把它从队尾弹出。
看到要从队尾弹出元素,显然要用双端队列(std::deque)实现。
然后我们再把 \(5\) 入队,此时的队列如下:
1 5
此时发现入队元素达到了 \(3\) 个,因为目前队里的元素都在第一个长度为 \(3\) 的区间中,所以队头 \(1\) 是该区间最小值。
然后再遍历到下一个 \(14\),在入队之前,我们发现队头的 \(1\) 已经不在第二个区间中,所以不可能成为后面的最小值,将其弹出。
这个 \(14\) 和上一个 \(14\) 不同,因为它在 \(5\) 之后,我们无法保证它在之后的区间不会成为最小值,只能将其老老实实入队,此时的队列如下:
5 14
此时队中的元素都是第二个区间的元素,区间最小值即为队头 \(5\)。
依此模拟下去,即可求出所有区间最小值。
我们发现,单调队列题目的套路如下:
-
把队头所有“过期”的元素弹出。
-
发现一个新元素,将队尾破坏单调性的元素(例如上例的第一个 \(14\))弹出队尾,插入这个元素,保证队列元素的单调。
-
队头往往构成最优。
-
把当前元素入队。
简化一下:
把队头队尾不可能在未来构成最优的元素弹出,取队头为当前区间最优,并将当前元素入队。
由于每个元素入队一次,至多出队一次,所以总复杂度为 \(O(n)\)。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 9;
int n,m;
int a[N];
deque<int>q1;
int main(){
scanf("%d%d", &n, &m);
for(int i = 1;i <= n;i++)
scanf("%d", &a[i]);
for(int i = 1;i <= n;i++){
while(!q1.empty() && q1.front() + m < i)
q1.pop_front();
while(!q1.empty() && a[q1.back()] >= a[i])
q1.pop_back();
if(i == 1)
printf("0\n");
else
printf("%d\n", a[q1.front()]);
q1.push_back(i);
}
return 0;
}

浙公网安备 33010602011771号