【学习笔记】单调队列

建议结合板题食用。

单调队列,顾名思义,就是队内元素满足单调性的队列,可以用于在线性的时间复杂度内求一个序列所有长度为 k 的区间的最值。

先看一下这个序列:

1 14 5 14 19 1 98 10

设这里的 k=3,我们先维护一个队列:

首先,我们将 114 入队,此时的队列如下:

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 @   5t0_0r2  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示