【学习笔记】单调队列

建议结合板题食用。

单调队列,顾名思义,就是队内元素满足单调性的队列,可以用于在线性的时间复杂度内求一个序列所有长度为 \(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\)

依此模拟下去,即可求出所有区间最小值。

我们发现,单调队列题目的套路如下:

  1. 把队头所有“过期”的元素弹出。

  2. 发现一个新元素,将队尾破坏单调性的元素(例如上例的第一个 \(14\))弹出队尾,插入这个元素,保证队列元素的单调。

  3. 队头往往构成最优。

  4. 把当前元素入队。

简化一下:

把队头队尾不可能在未来构成最优的元素弹出,取队头为当前区间最优,并将当前元素入队。

由于每个元素入队一次,至多出队一次,所以总复杂度为 \(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;
}
posted @ 2024-01-26 19:16  5t0_0r2  阅读(80)  评论(0)    收藏  举报