当前位置: 首页 > news >正文

单调栈与单调队列

单调栈与单调队列

你说我都会树链剖分了,咋还不会单调栈与单调队列啊?

单调栈

问题类型:对于一个长度为 nnn 的序列 aaa,对于 ∀1≤i≤n\forall 1\le i\le n∀1in,求 [a1∼ai−1][a_1\sim a_{i-1}][a1ai1][ai+1∼an][a_{i+1}\sim a_n][ai+1an] 的最靠近 iii 且比 aia_iai 大/小的问题。

以在 iii 左边的区间为例。

不过说最值问题可以用线段树、ST 表、树状数组之类的,为啥要学这个嘞?

  • 实现简单
  • 速度较快,毕竟线段树、树状数组常数都挺大的
  • 不会其他的:

那么我们来看看原理。

不妨先手搓一个样例玩玩。

假设数组为 [1,2,3,4,5][1,2,3,4,5][1,2,3,4,5],求最大值。

那么我们挨个看看 iii

iii数列
111[1][1][1]
222[1,2][1,2][1,2]
333[1,2,3][1,2,3][1,2,3]
444[1,2,3,4][1,2,3,4][1,2,3,4]
555[1,2,3,4,5][1,2,3,4,5][1,2,3,4,5]

发现第 iii 次操作有且仅有 aia_iai 被新加入数列。即 [a1∼ai−1][a_1\sim a_{i-1}][a1ai1] 是不变的。如果暴力枚举就会重复,效率极低。

所以我们考虑贪心的思想。

因为是求最大值,如果 ∃ i<j,ai≤aj\exist \ i<j,a_i\le a_j i<j,aiaj 的情况,是不是从现在开始,在 aia_iai 的有生之年就不可能对答案有贡献。

因为后面的区间如果包含了 aia_iai,也必定包含了 aja_jaj,所以 aia_iai 一定会被 aja_jaj 所替代,也就无法产生贡献。

那我们就不需要考虑 aia_iai,可以把它踢出去。

所以我们存储在序列里的元素一定是一个具有单调性的序列。

我们考虑用栈进行存储。

如果 i<j,ai≤aji<j,a_i\le a_ji<j,aiaj,即 aia_iai 已成为废品,那么就让它从栈中弹出。重复执行这一步骤。

最后再加入 aja_jaj 就算完成了一次操作。

来看看代码。

for(int i=1;i<=n;++i){while(!st.empty()&&h[st.top()]<h[i]){ans[st.top()]=i;st.pop();}st.push(i);}

虽然看起来是双层循环,是 O(n2)O(n^2)O(n2),但是如果仔细算算,发现 while 里面的操作总共最多执行 nnn,所以总时间复杂度是 O(n)O(n)O(n)

单调队列

单调栈的升级版。升级在于规定了区间长度为 mmm

即当前为 iii,与 iii 有关系的区间为 [i−m−1,i−1][i-m-1,i-1][im1,i1][i+1,i+m+1][i+1,i+m+1][i+1,i+m+1]。求最值。

以在 iii 左边的区间为例。

那么我们再来手搓玩玩。序列还是 [1,2,3,4,5][1,2,3,4,5][1,2,3,4,5],区间长度为 333

iii序列
111[1][1][1]
222[1,2][1,2][1,2]
333[1,2,3][1,2,3][1,2,3]
444[2,3,4][2,3,4][2,3,4]
555[3,4,5][3,4,5][3,4,5]

其中只有 i∈{3,4,5}i\in \left\{ 3,4,5\right\}i{3,4,5} 时才有意义。所以只需要考虑 3≤i≤53\le i\le 53i5

可能不够直观,换种方式看看?
[1 2 3] 4 5 1 [2 3 4] 5 1 2 [3 4 5] [1\ 2\ 3]\ 4\ 5\\ \ 1\ [2\ 3\ 4]\ 5\\ \ 1\ 2\ [3\ 4\ 5]\\ [1 2 3] 4 5 1 [2 3 4] 5 1 2 [3 4 5]
注意到,每次改动仅是多加上一个数 ai+1a_{i+1}ai+1,并减去一个数 ai−m+1a_{i-m+1}aim+1。所以说就是单调栈的升级版。

俗称滑动窗口。感觉十分形象。

这个是真正的区间最值。但上文说过,线段树、ST 表、树状数组都太慢且不容易实现。

老规矩,还是维护一个具有单调性双端队列,这样可以模拟从尾部进,从队头出。

对于每次“滑动”,先将滑动所加元素加入队列尾部,不断踢出不比其更优的元素。这个原理和单调栈相似。

然后由于是滑动,所以队列前面的元素可能已经超过了窗口,那我们将它踢出队列。

最后将新加元素加到队列尾部。

代码:

for(int i=1;i<=n;++i){while(!dq.empty()&&a[dq.back()]<=a[i])dq.pop_back();while(!dq.empty()&&dq.front()<=i-k)dq.pop_front();dq.push_back(i);ans[i]=a[dq.front()];}

完结撒花。

http://www.dtcms.com/a/364016.html

相关文章:

  • 《从零搭建二叉树体系:从节点定义到子树判断的实战指南(含源码可直接运行)》
  • 利用Base64传输二进制文件并执行的方法(适合没有ssh ftp等传输工具的嵌入式离线场景)
  • TDK InvenSense CH201距离传感器
  • Photoshop用户必看:让你的PSD像JPG一样可预览
  • vim中常见操作及命令
  • 趣说IT职场30:跨团队会议话术合集:优雅反对、不留记录
  • 使用DataLoader加载本地数据
  • Elasticsearch 核心特性与应用指南
  • 【js】Promise.try VS try-catch
  • 研发文档分散在本地和邮件里如何集中管理
  • 面试必避坑:MySQL 自增 ID 用尽问题深度解析与应对策略
  • XML在线格式化 - 加菲工具
  • 双Token实战:从无感刷新到安全防护,完整流程+代码解析
  • 魔域服务器多少钱一个月?魔域服务器配置要求及推荐
  • Vue 3.5 重磅新特性:useTemplateRef 让模板引用更优雅、更高效!
  • 服务器托管需要注意什么事项?
  • 人工智能助力流感疫苗选择:MIT 团队推出 VaxSeer 系统
  • MySQL注意事项与规范
  • 开发AI编程工具的方案分析
  • SPI片选踩坑实录(硬件片选和软件片选)
  • Nacos配置文件攻防思路总结|揭秘Nacos被低估的攻击面|挖洞技巧
  • Python 基础核心概念与实战代码示例(含数据类型、变量、流程控制、数据结构、函数与文件操作)
  • # Shell 文本处理三剑客:awk、sed 与常用小工具详解
  • 如何修改 Docker 默认网段(网络地址池)配置:以使用 10.x.x.x 网段为例
  • 2024 年 AI 产业格局复盘:头部企业竞逐方向与中小玩家生存破局点
  • 跨境电商账号风控核心:IP纯净度与浏览器指纹的防护策略
  • 基于单片机车流车速检测系统设计
  • 90%的C++ 程序员都忽略了这个容器——unordered_multiset,让我们来看看开源项目中怎么使用的
  • 最小二乘法之线性回归篇(普通最小二乘OLS、加权最小二乘WLS、广义最小二乘GLS)-原理讲解
  • 毕业项目推荐:69-基于yolov8/yolov5/yolo11的轴承缺陷检测识别系统(Python+卷积神经网络)