单调栈&单调队列学习笔记 write by taoyiwei17_cfynry days:2024.1.7

单调队列

定义

是指队列维护的元素单调、下标也单调的数据结构。

单调队列不像优先队列,是一种C++自带的STL,单调队列是用普通的队列进行维护的。

使用场景

滑动窗口。

在一个固定大小的窗口内寻找最值,且窗口从左到右移动。

滑动窗口实现方法

  1. 暴力: O ( n 2 ) O(n^2) O(n2)

  2. 线段树: O ( n × log ⁡ 2 n ) O(n \times \log_2{n}) O(n×log2n)。

  3. RMQ: O ( n × log ⁡ 2 n ) O(n \times \log_2{n}) O(n×log2n)

  4. 单调队列: O ( n ) O(n) O(n)

显然,用单调队列维护时间复杂度最优。

单调队列实现步骤

  1. 循环枚举下标 i i i,从 1 1 1 到 n n n。

  2. a i a_i ai 循环与队尾元素 a q t a i l a_{q_{tail}} aqtail 比较,并删除 ≤ a i \le a_i ≤ai 的队尾, i i i 进队尾。

  3. 检查队头是否过期,并从队头删除过期下标。

  4. 输出队头元素,即为当前窗口最大值(具体看题目)。

时间复杂度分析:每个元素只出队入队一次,总时间复杂度为 O ( n ) O(n) O(n)

例题

First:P1886P2032

单调队列模板题。

系列难度:P2032 < < < P1886

放个P1886的代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int a[N], q[N], n, k, head=1, tail;
void getmin()
{
	head=1, tail=0;
	for(int i=1;i<=n;i++)
	{
		while(head<=tail&&a[q[tail]]>=a[i])
		{
			tail--;
		}
		q[++tail]=i;
		while(head<=tail&&q[tail]-q[head]+1>k)
		{
			head++;
		}
		if(i>=k)cout<<a[q[head]]<<" ";
	}
	cout<<"\n";
}
void getmax()
{
	memset(q,0,sizeof(q));
	head=1, tail=0;
	for(int i=1;i<=n;i++)
	{
		while(head<=tail&&a[q[tail]]<=a[i])
		{
			tail--;
		}
		q[++tail]=i;
		while(head<=tail&&q[tail]-q[head]+1>k)
		{
			head++;
		}
		if(i>=k)cout<<a[q[head]]<<" ";
	}
	cout<<"\n";
}
signed main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	getmin();
	getmax();
}

Second:P1638P1440

稍微包装了一下的模板题。

系列难度:P1638 > > > P1440

Third:P1714

单调队列中套了一个简单算法,这次套的前缀和。

Fourth:P2698P2564

难度上升了不止一点。

P2698在二次单调队列中套了个二分。

难度都差不多,P2564有一些坑点,P2698难写一些。

P2698也有坑点,和单调队列窗口大小有关。

总结

单调队列虽然是线段树等数据结构中难度中下级的数据结构,但与其他算法结合起来难度还是不小的。

单调队列例题

讲个难点的,P2698

分析

  1. 由于每滴水以 1 1 1 单位时间下降,所以时间差等于高度差。

  2. 假设花盆宽度 w w w 已经确定,那么花盆可以从左到右滑动,转化为滑动窗口。

  3. 维护 2 2 2 个单调队列,维护最小和最大 y y y 值。

  4. 枚举花盆宽度可行但会TLE,宽度有单调性,一眼二分。

注意事项:

  1. 枚举将水滴按 x x x 轴 s o r t sort sort。(水滴比坐标小,枚举水滴常数低)

  2. 花盆宽度为 n n n 时,可接到 [ i , i + w ] [i,i+w] [i,i+w] 的水滴。

贴个 check 函数

cpp 复制代码
bool check(int x)
{
	head=1,tail=0,head1=1,tail1=0;
	for(int i=1;i<=n;i++)
	{
		while(head<=tail&&a[i].y>=a[q[tail]].y)
		{
		    tail--;
		}
		q[++tail]=i;
		while(tail-head>=0&&a[q[tail]].x-a[q[head]].x+1>x)
		{
		    head++;
		}
		while(head1<=tail1&&a[i].y<=a[qq[tail1]].y)
		{
		    tail1--;
		}
		qq[++tail1]=i;
		while(tail1-head1>=0&&a[qq[tail1]].x-a[qq[head1]].x+1>x)
		{
		    head1++;
		}
		if(abs(a[q[head]].y-a[qq[head1]].y)>=m)
		{
			return 1;
		}
	}
	return 0;
}

单调栈

定义

一种下标单调、元素也单调的栈。

单调栈同单调队列不是一种C++自带的STL,单调栈是用普通的栈进行维护的。

使用场景

在若干区间内找最值,转化为枚举每个最值找区间。

寻找每个元素 a i a_i ai 向右(左)第一个比 a i a_i ai 大(小)的元素位置。

如何寻找 a i a_i ai 右边第一个大于 a i a_i ai 的位置?

  1. 枚举 i i i, a i a_i ai 与 s t k . t o p ( ) stk.top() stk.top() 循环比较,若 a i a_i ai 大于当前栈顶元素,弹出栈顶。

  2. i i i 入栈。

  3. 循环结束后,剩余栈中元素下标说明其右侧没有更大的元素。

如何寻找 a i a_i ai 左边第一个大于 a i a_i ai 的位置?

同上,倒序枚举 i i i 即可。

训练题单

  1. P5788 模板题

  2. P2947 跟模板题差不多,套了个背景。

  3. P2866 初学者建议去做,码量短,有少许思维难度。

  4. CF547B 做法多样,难度中等偏上少许,有一定思维难度,记得看讨论区的翻译。

  5. CF1299C 这题建议已开始学CSP-S相关内容的同学做,难度思维码量都有的。

模板代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e6+5;
int n, a[N], ans[N];

stack<int> stk;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		a[n+1]=1e9;
	}
	for(int i=1;i<=n;i++)
	{
		while(!stk.empty()&&a[i]>a[stk.top()])
		{
			ans[stk.top()]=i;
			stk.pop();
		}
		stk.push(i);
	}
	for(int i=1;i<=n;i++)
	{
		cout<<ans[i]<<" ";
	}
	return 0;
}
相关推荐
坚硬果壳_2 分钟前
《硬件架构的艺术》笔记(一):亚稳态
笔记·学习
醉颜凉26 分钟前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
糊涂君-Q28 分钟前
Python小白学习教程从入门到入坑------第三十一课 迭代器(语法进阶)
python·学习·程序人生·考研·职场和发展·学习方法·改行学it
qiyi.sky31 分钟前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat
lapiii35835 分钟前
图论-代码随想录刷题记录[JAVA]
java·数据结构·算法·图论
dal118网工任子仪1 小时前
web安全漏洞之ssrf入门
笔记·学习·计算机网络·网络安全
键盘敲没电1 小时前
【iOS】知乎日报前三周总结
学习·ios·objective-c·xcode
Lyqfor1 小时前
云原生学习
java·分布式·学习·阿里云·云原生
Dontla2 小时前
Rust泛型系统类型推导原理(Rust类型推导、泛型类型推导、泛型推导)为什么在某些情况必须手动添加泛型特征约束?(泛型trait约束)
开发语言·算法·rust
大懒的猫猫虫2 小时前
Upload-Labs-Linux1学习笔迹 (图文介绍)
学习