首页 > 其他分享 >单调队列笔记

单调队列笔记

时间:2024-11-10 15:42:56浏览次数:4  
标签:队列 ++ 笔记 int hh front 单调

单调队列笔记

双端队列deque维护一个严格单调变化的组,可以称为一个单调队列

单调队列因为可以直接对组的两端进行操作,所以可以有效的降低时间复杂度

用单调队列来解决问题,一般是需要得到的某个范围内的最小值或最大值

这里以一道经典的单调队列的题目为例:


题目描述

有一个长为 \(n\) 的序列 \(a\),以及一个大小为 \(k\) 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如,对于序列 \([1,3,-1,-3,5,3,6,7]\) 以及 \(k = 3\),有如下过程:

\[\def\arraystretch{1.2} \begin{array}{|c|c|c|}\hline \textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \\ \hline \verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \\ \hline \verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \\ \hline \verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \\ \hline \verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \\ \hline \end{array} \]

输入格式

输入一共有两行,第一行有两个正整数 \(n,k\)。
第二行 \(n\) 个整数,表示序列 \(a\)

输出格式

输出共两行,第一行为每次窗口滑动的最小值
第二行为每次窗口滑动的最大值

样例输入

8 3
1 3 -1 -3 5 3 6 7

样例输出

-1 -3 -3 -3 3 3
3 3 5 5 6 7

数据范围
对于 \(50\%\) 的数据,\(1 \le n \le 10^5\);
对于 \(100\%\) 的数据,\(1\le k \le n \le 10^6\),\(a_i \in [-2^{31},2^{31})\)。


题目所给的窗口 ,我们可以使用一个单调队列来维护,每次移动时,检查新进窗口的元素,和退出窗口的元素

然后输出单调队列的最小值或最大值

可以将朴素做法(使用两次循环)的 \(O(n*k)\) 的时间复杂度优化到 \(O(n+k)\),得到极大的改进,减少了重复的比较次数


首先的是单调队列的写法,可以采用数组模拟队列的方法或者STL库内的deque

这里先介绍数组模拟的方式:

  • 定义数组队列、队列的头、队列的尾
int que[1000010], hh, tt;
  • 然后将头和尾元素进行初始化:
int hh = 0, tt = -1;

hh一开始指向的是a[]的第一个元素的下标 \(0\) ,此时队列中的元素个数为 \(0\),故tt=-1

注意!!!这里的单调队列存储的是a[]数组的下标

先来输出窗口内的最小值:
  • 遍历a[]数组
for (int i = 0; i < n; i++) {
	......
}
  • 其中,我们需要进行的是队列的维护
if (hh <= tt && i - k + 1 > q[hh]) hh++;

hh<=tt即队列不为空时,若队列中头元素存储的下标值不在a[]数组的窗口内,那么就将头元素向后移动一位(在严格单调队列中),(相当于弹出头元素)

while (hh <= tt && a[q[tt]] >= a[i]) tt--;
q[++tt] = i;

hh<=tt即队列不为空时,若新进窗口的元素小于我们单调队列的尾元素存储的下标对应的值,就将尾元素向前移动(相当于弹出队尾元素),直到插入a[i]的下标后形成一个严格单调队列

a[i]的下标作为尾元素,存进队列中

if (i >= k - 1) printf("%d ", a[q[hh]]);

判断窗口是否完整,输出窗口中的最小值

同理,可以依葫芦画瓢地写出输出窗口中最大值的代码:
hh = 0, tt = -1;//重新初始化
for (int i = 0; i < n; i++) {
	if (hh <= tt && i - k + 1 > q[hh]) hh++;
	while (hh <= tt && a[q[tt]] <= a[i]) tt--;
	q[++tt] = i;
	if (i >= k - 1) printf("%d ", a[q[hh]]);
}

下面是使用deque实现的代码:

deque<int> q;

定义队列 q,同样的思想可以得到如下代码:

for (int i = 0; i < n; i++) {
	while (!q.empty() && i - k + 1 > q.front()) q.pop_front();
	while (!q.empty() && a[i] <= a[q.back()]) q.pop_back();
	q.push_back(i);
	if (i - k + 1 >= 0) printf("%d ", a[q.front()]);
}

q.empty()判断队列是否为空,q.front()返回队头元素、q.back()返回队尾元素

q.pop_front()弹出队头元素、q.pop_back()弹出队尾元素

最后使用q.clear()清空队列,同理得到剩余部分的代码

核心AC代码如下:

模拟队列:

int n, k;
int a[1000010], q[1000010];

int main()
{
	scanf("%d %d", &n, &k);
	for (int i = 0; i < n; i++) scanf("%d", &a[i]);
	int hh = 0, tt = -1;
	for (int i = 0; i < n; i++) {
		if (hh <= tt && i - k + 1 > q[hh]) hh++;
		while (hh <= tt && a[q[tt]] >= a[i]) tt--;
		q[++tt] = i;
		if (i >= k - 1) printf("%d ", a[q[hh]]);
	}
	printf("\n");
    hh = 0, tt = -1;
	for (int i = 0; i < n; i++) {
		if (hh <= tt && i - k + 1 > q[hh]) hh++;
		while (hh <= tt && a[q[tt]] <= a[i]) tt--;
		q[++tt] = i;
		if (i >= k - 1) printf("%d ", a[q[hh]]);
	}
	return 0;
}

使用STL库:

int n, k;
int a[1000010];
deque<int> q;

int main()
{
	scanf("%d %d", &n, &k);
	for (int i = 0; i < n; i++) scanf("%d", &a[i]);
	for (int i = 0; i < n; i++) {
		while (!q.empty() && i - k + 1 > q.front()) q.pop_front();
		while (!q.empty() && a[i] <= a[q.back()]) q.pop_back();
		q.push_back(i);
		if (i - k + 1 >= 0) printf("%d ", a[q.front()]);
	}
	printf("\n");
	q.clear();
	for (int i = 0; i < n; i++) {
		while (!q.empty() && i - k + 1 > q.front()) q.pop_front();
		while (!q.empty() && a[i] >= a[q.back()]) q.pop_back();
		q.push_back(i);
		if (i - k + 1 >= 0) printf("%d ", a[q.front()]);
	}
	return 0;
}

标签:队列,++,笔记,int,hh,front,单调
From: https://www.cnblogs.com/dianman/p/18538072

相关文章

  • FWT 学习笔记
    快速沃尔什变换模板题给定长度为\(2^n\)两个序列\(A,B\),设\[C_i=\sum_{j\oplusk=i}A_j\timesB_k\]分别当\(\oplus\)是or,and,xor时求出\(C\)。FWT,中文名称:快速沃尔什变换。因为已经有FFT和NTT基础,所以直接考虑构造FWT的变换。不失一般性,先考虑\(n=......
  • 【吴恩达机器学习笔记】10-正则化解决过拟合问题
    过拟合是机器学习中一个常见的问题,它发生在模型在训练数据上表现得很好,但在未见过的测试数据上表现不佳时。这通常是因为模型过于复杂,捕捉到了训练数据中的噪声和细节,而没有学习到数据的一般模式。过拟合的定义过拟合是指模型在训练数据上能够获得比其他假设更好的拟合,但在训......
  • 新手上云实践:在腾讯云CVM上使用Docker部署Leanote开源笔记工具
    新手上云实践:在腾讯云CVM上使用Docker部署Leanote开源笔记工具前言一、云服务器CVM介绍1.1CVM简介1.2CVM主要特点1.3CVM主要使用场景二、本次环境规划2.1本次实践简介2.2本次环境规划三、购买CVM云服务器3.1腾讯云双十一活动3.2购买云服务器CVM3.3检查CVM云服......
  • 平板 在实际生活中具体有什么用途?学习记笔记
    平板在实际生活中具体有什么用途?学习记笔记--------------我的ipad就是。。上课/开会的时候,打开Notability,开启新页面,点击录音,开始做笔记。——不过实际上用到录音的机会也就5次左右,笔记看的也不多。主打一个自欺欺人了属于是。哦,还有ipad远程到电脑,临时改一下代码。。。https:......
  • 栈和队列(原理、代码实现、例题)
    一、栈1.概念栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(LastInFirstOut)的原则。压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。出栈:栈的删除操作叫做......
  • Markdown 学习笔记 (基于Typora)
    Markdown学习笔记(基于Typora)目录Markdown学习笔记(基于Typora)基础语法进阶语法基础语法标题:###某三级标题(一级到六级)。加粗:**要加粗的部分**,或者Ctrl+B。斜体:*要斜体的部分*,或者Ctrl+I。删除线:~~要删除的部分~~。高亮(扩展语法):==要高亮的部分==单行代码:`......
  • 面对对象编程:OOP类与对象详细笔记
    文章目录类类的概念:类的定义:类的属性(成员变量):**类的方法(成员方法):**对象对象的创建与使用:this关键字:构造方法:类与对象的关系:封装:无参方法有参方法类类的概念:类是一种用户定义的数据类型,它描述了一组具有相同特性和行为的对象。类定义了对象的状态(属性)和行为(方法)。......
  • Living-Dream 系列笔记 第84期
    连通性问题点双连通:在无向图中,删除一个点(不是\(x\)或者\(y\))后,点\(x\)和点\(y\)仍然能够彼此到达,那么称\(x\)和\(y\)是点双连通的。边双连通:在无向图中,删除一条边后,点\(x\)和点\(y\)仍然能够彼此到达,那么称\(x\)和\(y\)是边双连通的。性质点双连......
  • Living-Dream 系列笔记 第85期
    割边在无向图中删了一条边后,图中联通块个数增加,则称该边为割边。判定对于一条\(cur\toi\)的边,若\(low_i>dfn_{cur}\)(不能取等,画图便知理由),则该边为割边。T103481&P1656板子。P1656code#include<bits/stdc++.h>usingnamespacestd;constintN=1e5+5;intn,......
  • Ch4 不定积分 --学习笔记
        简单来讲,求解积分的过程实际上就是求导的逆过程,微分与积分互为互逆运算。4.1.1不定积分的概念 定义1设函数f(x)是定义在区间I上的函数,若存在函数F(x),对于任意x∈I都有F'(x)=f(x)或dF(x)=f(x)dx,则称F(x)是f(x)在区间I上的一个原函数        对于此......