算法自学__单调栈

news/2024/4/19 10:55:38/文章来源:https://blog.csdn.net/MaTF_/article/details/129662755

参考资料:

  • https://zhuanlan.zhihu.com/p/346536592

算法简介

单调栈可以在 O(n)O(n)O(n) 的时间复杂度内找到序列中每个元素的下一个比它大 / 小的元素。

以找到每个元素的下一个比它大的元素(简称 NGE )为例。单调栈的基本思想是在遍历序列的过程中维护一个栈,栈中为还未找到 NGE 的元素。于是,当访问到一个元素 i 时,应该先将栈中所有比该元素小的元素出栈,并记录这些元素的 NGE 为 i ,然后再让 i 入栈。显然,栈中元素始终是单调递减的(栈底最大)。

例1 P1901 发射站

题目大意

n 个发射站,每个发射站的属性为高度发射的能量值。每个发射站发出的能量,只能被其两侧第一个比它的发射站接收到。问接受能量最多的发射站接收的能量是多少。

思路

单调栈找 NGE 模板题。

代码

#include<bits/stdc++.h>
using namespace std;const int maxn = 1e6+5;int n;
int v[maxn], h[maxn];
int sum[maxn];
int pre[maxn];
int nxt[maxn];
int ans = -1;int main(){cin>>n;for(int i=1;i<=n;i++){cin>>h[i]>>v[i];}stack<int> s1, s2;for(int i=1;i<=n;i++){while(!s1.empty() && h[i]>h[s1.top()]){sum[i] += v[s1.top()];s1.pop();}s1.push(i);}for(int i=n;i>=1;i--){while(!s2.empty() && h[i]>h[s2.top()]){sum[i] += v[s2.top()];s2.pop();}s2.push(i);}for(int i=1;i<=n;i++){ans = max(ans, sum[i]);}cout<<ans;return 0;
} 

例2 Skyscrapers (hard version)

题目大意

n 座楼,第 i 座楼限高为 m[i] ,且必须保证每座楼的左右两侧(不需要相邻)不能同时有比它高的楼,输出所有楼高度之和最大的一种方案。

思路

由题目可知,所有楼的实际高度呈先上升再下降的趋势,构成一个“单峰”函数。所以,问题的关键在于求出“峰”的位置。考虑最朴素的做法,给定一个峰的位置,可以在 O(n)O(n)O(n) 的时间复杂度内求出最大的高度之和,所以总的时间复杂度为 O(n2)O(n^2)O(n2)

考虑使用单调栈优化的 dp 。定义状态 dpl[i] 表示:以楼 i 为峰,楼 i 及其左侧楼的最大高度和;定义状态 dpr[i] 表示:以楼 i 为峰,楼 i 及其右侧楼的最大高度和;定义状态 sum[i] 表示:以楼 i 为峰,所有楼的最大高度和。于是有:sum[i] = dpl[i] + dpr[i] - m[i] 。此时,问题的关键在于如何在 O(n)O(n)O(n) 的时间复杂度内求出所有的 dpl[i]dpr[i]

以求 dpr[i] 为例。我们先使用单调栈求出每座楼右侧第一个楼高小于等于当前楼的位置,记录在 nxt[] 中。我们从右向左遍历所有楼,假设当前访问到楼 i ,则区间 [i+1, nxt[i]-1] 的所有楼都比 i 高,所以这些楼的实际长度都为 m[i] ;而楼 nxt[i] 高度小于等于楼 i ,所以其可以取到峰值,此时,楼 nxt[i] 及其右侧的楼高之和已经保存在了 dpr[nxt[i]] 中。于是,我们得到了状态转移方程:

dpr[i] = (nxt[i]-i)*m[i] + dpr[nxt[i];

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;const int maxn = 5e5+5;int n;
int m[maxn];
int pre[maxn], nxt[maxn];
int dpl[maxn], dpr[maxn];
int sum[maxn];
int M = -1;
int pos = 0;
stack<int> sl, sr;signed main(){cin>>n;for(int i=1;i<=n;i++){cin>>m[i];// 注意初始化 nxt[] 数组nxt[i] = n+1;}for(int i=1;i<=n;i++){while(!sl.empty() && m[i]<=m[sl.top()]){nxt[sl.top()] = i;sl.pop();}sl.push(i);}for(int i=n;i>=1;i--){while(!sr.empty() && m[i]<=m[sr.top()]){pre[sr.top()] = i;sr.pop();} sr.push(i);}for(int i=1;i<=n;i++){dpl[i] = dpl[pre[i]] + (i-pre[i])*m[i];}for(int i=n;i>=0;i--){dpr[i] = dpr[nxt[i]] + (nxt[i]-i)*m[i];}for(int i=1;i<=n;i++){sum[i] = dpl[i]+dpr[i]-m[i];if(sum[i] > M){M = sum[i];pos = i;}}for(int i=pos-1;i>=1;i--){m[i] = min(m[i+1], m[i]);}for(int i=pos+1;i<=n;i++){m[i] = min(m[i-1], m[i]);}for(int i=1;i<=n;i++){cout<<m[i]<<' ';}return 0;
}

例3 P1823 [COI2007] Patrik 音乐会的等待

题目大意

给定一个长度为 n 的序列 h[] ,求这个序列中有多少组下标 i, j 满足:区间 [i+1, j-1] 内的所有元素的值均小于等于 ij 中的较小者(若 i, j 相邻,也视为符合要求)。

思路

遍历序列中的每个结点,访问到结点 i 时,求出 i 之前能看到 i 的结点的数量,加到最终答案中。

回忆使用单调栈求 NGE 问题的过程,一旦确定了元素 i 的 NGE 是 j ,就意味着 i, j 满足题目中的条件。具体实现见代码。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;const int maxn = 5e5+5;struct NODE{int num;int cnt;NODE(int num=0, int cnt=0):num(num), cnt(cnt){}
};int h[maxn];
int n;
stack<NODE> s;
int ans = 0;signed main(){cin>>n;for(int i=1;i<=n;i++){cin>>h[i];}for(int i=1;i<=n;i++){int cnt = 0;while(!s.empty() && h[i]>=h[s.top().num]){// 当前元素与栈顶元素相等if(h[i] == h[s.top().num]){cnt = s.top().cnt;}ans += s.top().cnt;s.pop();}if(!s.empty()){ans++;}s.push(NODE(i, cnt+1));}cout<<ans;return 0;
}

例4 Discrete Centrifugal Jumps

题目大意

nnn 个高楼排成一行,每个楼有一个高度 hih_ihi。称可以从楼 iii 跳到 楼 jjj,当 iii, jjj ( i<ji < ji<j )满足以下三个条件之一:

  • i+1=ji+1=ji+1=j

  • max⁡(hi+1,hi+2,⋯,hj−1)<min⁡(hi,hj)\max(h_{i+1},h_{i+2},\cdots,h_{j-1})<\min(h_i,h_j)max(hi+1,hi+2,,hj1)<min(hi,hj)

  • min⁡(hi+1,hi+2,⋯,hj−1)>max⁡(hi,hj)\min(h_{i+1},h_{i+2},\cdots,h_{j-1})>\max(h_i,h_j)min(hi+1,hi+2,,hj1)>max(hi,hj)

现在你在楼 111,请求出跳到楼 nnn 最少要跳几次。

2≤n≤3⋅1052 \leq n \leq 3\cdot 10^52n3105, 1≤hi≤1091\leq h_i \leq 10^91hi109

思路

本题和例3类似。定义状态 dp[i] 表示:移动到楼 i 所需要的最少步数。

代码

#include<bits/stdc++.h>
using namespace std;const int maxn = 3e5+5;int h[maxn];
int dp[maxn];
int n;
stack<int> s1, s2;int main(){cin>>n;memset(dp, 0x7f, sizeof(dp));for(int i=1;i<=n;i++){cin>>h[i];}dp[1] = 0;for(int i=1;i<=n;i++){while(!s1.empty() && h[i]>h[s1.top()]){dp[i] = min(dp[i], dp[s1.top()]+1);s1.pop();}if(!s1.empty()){dp[i] = min(dp[i], dp[s1.top()]+1);if(h[s1.top()] == h[i]){s1.pop();}} s1.push(i);while(!s2.empty() && h[i]<h[s2.top()]){dp[i] = min(dp[i], dp[s2.top()]+1);s2.pop();}if(!s2.empty()){dp[i] = min(dp[i], dp[s2.top()]+1);if(h[s2.top()] == h[i]){s2.pop();}} s2.push(i);}cout<<dp[n];return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_274087.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 网络编程学习笔记——二、IP 协议详解

一、IP 服务的特点 IP 协议为上层协议提供无状态、无连接、不可靠的服务&#xff1a; 无状态&#xff08;stateless&#xff09;&#xff1a;指 IP 通信双方不同步传输数据的状态信息&#xff0c;因此所有 IP 数据报的发送、传输和接收都是相互独立的、没有上下文关系。 缺点…

Redis五种核心数据结构的基本使用与应用场景

文章目录五种核心数据结构StringHashListSetZset五种核心数据结构 String 常用操作 # 存入字符串键值对 SET key value# 批量存储字符串键值对 MSET key value [key value ...] # 存入一个不存在的字符串键值对 SETNX key value# 获取一个字符串键值 GET key# 批量获…

外贸网站排名优化,掌握这些技巧让你事半功倍!

作为一名外贸网站站长&#xff0c;我们都希望自己的网站能够排名靠前&#xff0c;从而吸引更多的潜在客户&#xff0c;提高网站的流量和转化率。 但是&#xff0c;想要在谷歌搜索引擎中取得好的排名并不是一件容易的事情。 在这篇文章中&#xff0c;我将分享一些我个人的经验…

JMM解析(java内存模型)

1.JMM 主要定义了对于一个共享变量&#xff0c;另一个线程对这个共享变量执行写操作后&#xff0c;这个线程对这个共享变量的可见性。 2.JMM和JVM的区别&#xff1a; JMM是用于高并发的&#xff0c;抽象了线程和主存之间的关系&#xff0c;比如线程之间共享的变量必须存储在…

代码随想录算法训练营第四十八天|198.打家劫舍、213.打家劫舍II、337.打家劫舍III

LeetCode 198 打家劫舍题目链接&#xff1a;https://leetcode.cn/problems/house-robber/思路&#xff1a;dp数组的含义dp[i]表示前i个房间&#xff08;包括第i个房间&#xff09;所能偷到的最大金额递推公式有两种情况&#xff1a;1、偷了第i个房间那么此时第i-1个房间肯定是不…

Python基础之操作mysql数据库

Python 标准数据库接口为 Python DB-API&#xff0c;Python DB-API为开发人员提供了数据库应用编程接口。Python 数据库接口支持非常多的数据库&#xff0c;你可以选择适合你项目的数据库&#xff1a;GadFlymSQLMySQLPostgreSQLMicrosoft SQL Server 2000InformixInterbaseOrac…

一、简单了解ElasticSearch

目录一、ElasticSearch简介1.ES与关系型数据库对比2.什么是全文检索3.分词原理&#xff08;基于倒排索引&#xff09;二、核心概念1.索引index2.映射mapping3.字段filed4.字段类型type5.文档document6.集群cluster7.节点node8.分片9.副本三、搭建es单机版、集群版1.搭建es2.集成…

项目质量管理工作 不得不重视的4大关键点

1、三大视角确保项目质量 我们需要从客户视角、SOW视角和组织视角三大视角&#xff0c;确保项目的质量。 从客户视角方面出发&#xff0c;满足客户的要求&#xff0c;如项目交付的准时性、项目质量的保证等。我们需要全力保障客户对项目质量的要求。 从SOW视角确保项目质量&…

ffpmeg笔记:(2)学习一个开源小demo:qt+sdl+ffmpeg,计算时间戳

文章目录前言1.源码和编译方法1.1编译方法&#xff1a;2.源码简单介绍2.1 播放线程类 PlayThread2.1.1 计算当前播放进度时间2.2 主界面类 MainWindow2.2.1 在Qt widget中显示视频2.2.2 控制区域的自动隐藏和再现前言 这个小demo实现了下面的功能&#xff1a; 1.打开文件。 2.…

操作系统(2.4.5)--管程机制

1.管程的定义 利用共享数据结构抽象地表示系统中的共享资源&#xff0c;而把对该共享数据结构实施的操作定义为一组过程进程对共享资源的申请、释放和其它操作&#xff0c;都是通过这组过程对共享数据结构的操作来实现的&#xff0c;这组过程还可以根据资源的情况&#xff0c;或…

跟着本文走,告诉你五类walmart最爱产品

在我们跨境圈一直流传着一句话&#xff0c;选品选的好啊&#xff0c;红利吃饱饱。选品的重要性相信不用龙哥多说&#xff0c;选品基本上就是决定了店铺的未来。好的选品不用愁流量、也不用担心走不长久。今天龙哥就来聊聊在walmart上&#xff0c;哪些选品是值得发展的。walmart…

synchronized 加锁 this 和 class 的区别

synchronized 是 Java 语言中处理并发问题的一种常用手段&#xff0c;它也被我们亲切的称之为“Java 内置锁”&#xff0c;由此可见其地位之高。然而 synchronized 却有着多种用法&#xff0c;当它修饰不同对象时&#xff0c;其意义也是不同的&#xff0c;下面我们一起来看。 ​…

实在智能RPA受邀出席2023年东莞市数字赋能峰会,聚力数智制造

3月17日&#xff0c;“数字东莞 科创强市2023年东莞市数字赋能峰会”在松山湖光大We谷圆满举行。本次大会以创新性、专业性、平台化、战略性等为特色&#xff0c;涵盖当今前沿技术、行业痛点、商业模式。会上中国信通院的专家分享了《东莞市数字经济发展报告&#xff08;2022年…

刷题day40:从中序与后序遍历序列构造二叉树 从前序与中序遍历序列构造二叉树

一、从中序与后序遍历序列构造二叉树 题意描述&#xff1a; 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 需要利用后续遍历&#xff08;左右中…

java校园报修管理系统ssm

用户的主要功能有&#xff1a; 1.用户注册和登陆登陆系统 2.用户查看报修类型&#xff0c;在线申请报修信息 3.用户对报修信息进行评价 4.用户查看资讯公告信息 5.用户查看个人信息&#xff0c;修改个人信息&#xff0c;修改密码 6.用户在线提交意见反馈信息 7.用户查看报修记录…

记录一次接口套娃数据处理

由于后端接口设计历史遗留问题&#xff0c;要求在一个接口中&#xff0c;通过他返回的数据去请求其他接口&#xff0c;数据以表格的形式渲染出来 目录 前言 一、每次仅展示一个步骤图 二、整合接口数据&#xff0c;一次性渲染 1.请求步骤条接口的地方对数据进行处理 2.修改…

完全小白的pycharm深度学习调试+for循环断点条件设置

完全小白的pycharm深度学习调试for循环断点条件设置写在最前面基础方法pycharm断点调试控制台输入代码中循环的debug方法pycharm中图标的介绍常见的BugDebug经验1. 检查激活函数的输入值2. 检查梯度3. 消融实验4. 使用最短的时间5. 静下心来写在最前面 之前把seq2seqattention…

简单分析Linux内核基础篇——initcall

写过Linux驱动的人都知道module_init宏&#xff0c;因为它声明了一个驱动的入口函数。 除了module_init宏&#xff0c;你会发现在Linux内核中有许多的驱动并没有使用module_init宏来声明入口函数&#xff0c;而是看到了许多诸如以下的声明&#xff1a; static int __init qco…

C++基础算法③——排序算法(选择、冒泡附完整代码)

排序算法 1、选择排序 2、冒泡排序 1、选择排序 基本思想&#xff1a;从头至尾扫描序列&#xff0c;每一趟从待排序元素中找出最小(最大)的一个元素值&#xff0c;然后与第一个元素交换值&#xff0c;接着从剩下的元素中继续这种选择和交换方式&#xff0c;最终得到一个有序…

Redis(十四)【Redisson分布式锁基础介绍】

分布式锁 Redisson 一、Redisson 概述 什么是 Redisson Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;。它不仅提供了一系列的分布式的Java常用对象&#xff0c;还提供了许多分布式服务。 Redisson 的宗旨是促进使…