【单调栈】【概念讲解模板代码】
文章目录
- 什么是单调栈
- 模拟实例:
- 一、【模板】单调栈
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 题目解析:
- 二、发射站
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 解题思路:
- 三、双子序列最大和
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 样例 #2
- 样例输入 #2
- 样例输出 #2
- 提示
- 解题思路:
什么是单调栈
单调栈就是单调的栈
(艹这不是废话吗 )
单调栈维护的就是一个数前/后第一个大于/小于他的数。
这样说来好像没啥,那就让我们来个具体的实例体验一下吧:
模拟实例:
有个集训队招人,一个数代表了一个选手的能力值,先进来的选手年龄会比较大,后面的选手年龄比较小,但是这个集训队没有人数限制,那么如果遇到一个比你小还比你强的人那么准备退役吧。
比如有 5 个能力值分别是:
1 7 5 6 3
要加进来这个单调栈。
首先是 1 也就是 选手1 ,那么集训队没有人和他比较所以进入集训队。
单调栈的情况:1 。
然后是 7 也就是 选手2 ,那么我们可以发现,选手2 比 选手1 要小,并且 选手2 的能力比 选手1 要强,那么 选手1 留着也没啥用,淘汰!好残酷呀!
单调栈的情况:7 。
然后是 5 选手3 ,选手3 比 选手2 年龄要小,但是 选手3 的能力没有 选手2 强,那么 选手3 招进集训队。
单调栈的情况:7 5 。
那么接下来是 6 选手4 ,选手4 比 选手3 年龄要小,比他还要强, 选手3 淘汰!往后比较,发现 选手2 虽然比 选手4 要大,但是他能力很强!所以不会被淘汰,将 选手4 招进集训队。
单调栈的情况:7 6 。
最后是 3 选手5 ,选手5 比 选手4 要小,但是 选手4 的能力比 选手5 要强,所以 选手4 必定也比不过 选手2 ,但由于 选手5 小所以不淘汰他。
单调栈的情况:7 6 3 。
通过这个模拟我们发现,如果你很强,就算你年龄大也不会被淘汰,其实不是这样,在单调队列(比单调栈高级的东西)里面你就算再强也终究有时候会退役的,所以好好珍惜吧!
一、【模板】单调栈
题目描述
给出项数为 nnn 的整数数列 a1…na_{1 \dots n}a1…n。
定义函数 f(i)f(i)f(i) 代表数列中第 iii 个元素之后第一个大于 aia_iai 的元素的下标,即 f(i)=mini<j≤n,aj>ai{j}f(i)=\min_{i<j\leq n, a_j > a_i} \{j\}f(i)=mini<j≤n,aj>ai{j}。若不存在,则 f(i)=0f(i)=0f(i)=0。
试求出 f(1…n)f(1\dots n)f(1…n)。
输入格式
第一行一个正整数 nnn。
第二行 nnn 个正整数 a1…na_{1\dots n}a1…n。
输出格式
一行 nnn 个整数 f(1…n)f(1\dots n)f(1…n) 的值。
样例 #1
样例输入 #1
5
1 4 2 3 5
样例输出 #1
2 5 4 5 0
提示
【数据规模与约定】
对于 30%30\%30% 的数据,n≤100n\leq 100n≤100;
对于 60%60\%60% 的数据,n≤5×103n\leq 5 \times 10^3n≤5×103 ;
对于 100%100\%100% 的数据,1≤n≤3×1061 \le n\leq 3\times 10^61≤n≤3×106,1≤ai≤1091\leq a_i\leq 10^91≤ai≤109。
题目解析:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int maxn=3e6+5;
int a[maxn];
int f[maxn];//f(i) 代表数列中第 i 个元素之后第一个大于 ai 的下标
stack<int> s;//存的是元素的下标
//单调栈
void solve(){int n;cin >> n;for (int i = 1; i <= n; i++)cin >> a[i];for (int i = n; i >= 1; i--){//从后往前扫描while (!s.empty() && a[s.top()] <= a[i])//栈顶元素小于当前这个数s.pop();//出栈f[i] = s.empty() ? 0 : s.top();//存入第一个大于的位置s.push(i);//当前元素入栈}for (int i = 1; i <= n; i++)cout << f[i] << " ";
}int main(){//ios::sync_with_stdio(0);solve();return 0;
}
二、发射站
题目描述
某地有 NNN 个能量发射站排成一行,每个发射站 iii 都有不相同的高度 HiH_iHi,并能向两边(两端的发射站只能向一边)同时发射能量值为 ViV_iVi 的能量,发出的能量只被两边最近的且比它高的发射站接收。显然,每个发射站发来的能量有可能被 000 或 111 或 222 个其他发射站所接受。
请计算出接收最多能量的发射站接收的能量是多少。
输入格式
第 111 行一个整数 NNN。
第 222 到 N+1N+1N+1 行,第 i+1i+1i+1 行有两个整数 HiH_iHi 和 ViV_iVi,表示第 iii 个人发射站的高度和发射的能量值。
输出格式
输出仅一行,表示接收最多能量的发射站接收到的能量值。答案不超过 32 位带符号整数的表示范围。
样例 #1
样例输入 #1
3
4 2
3 5
6 10
样例输出 #1
7
提示
对于 40%40\%40% 的数据,1≤N≤5000,1≤Hi≤105,1≤Vi≤1041\le N\le 5000,1\le H_i\le 10^5,1\le V_i\le 10^41≤N≤5000,1≤Hi≤105,1≤Vi≤104。
对于 70%70\%70% 的数据,1≤N≤105,1≤Hi≤2×109,1≤Vi≤1041\le N\le 10^5,1\le H_i\le 2\times 10^9,1\le V_i\le 10^41≤N≤105,1≤Hi≤2×109,1≤Vi≤104。
对于 100%100\%100% 的数据,1≤N≤106,1≤Hi≤2×109,1≤Vi≤1041\le N\le 10^6,1\le H_i\le 2\times 10^9,1\le V_i\le 10^41≤N≤106,1≤Hi≤2×109,1≤Vi≤104。
解题思路:
首先发出的能量只被两边最近的且比它高的发射站接收。
所以我们需要找到左右两边比当前高的最近的位置
考虑一下:
一个塔右端有大于他的塔,就等同于右端的一个塔的左端有一个小于它的塔。
每次维护单调栈的过程,就是寻找左端小于它的塔的过程。
然后我们用一个单调栈:当前位置左端的一个单调递减栈
一组塔的高度: 7 6 2 1 3 5 8
则当 i 塔为3
时
单调栈内:
栈顶 | 1 |
---|---|
2 | |
6 | |
栈底 | 7 |
然后我们判断h[st[top]] < h[i]
单调栈会比变为:
栈顶 | 6 |
---|---|
栈底 | 7 |
即 弹出来了 1
和 2
,所以弹出的塔就刚好是塔 3
左端小于 3
的塔
换句话说,塔 3
就是 1
和 2
右端最近的比他们高的塔
所以更新塔 3
接收的能量 sum[i] += v[st[top--]];
当 while
停止的时候 ,也就找到了塔 3
左端最近的大于它的塔 6
塔 6
接收塔 3
的能量:sum[st[top]] += v[i];
然后塔 3
入栈:
栈顶 | 3 |
---|---|
6 | |
栈底 | 7 |
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef vector<pair<int,int>> vpii;
#define all(a) a.begin(), a.end()
#define nl '\n'
#define debug() cout << "debug:"
const int inf = 0x3f3f3f3f;
const int maxn = 2e5+5;
//只被两边最近的且比它高的发射站接收
int n;
int h[maxn],v[maxn];
int st[maxn];//单调栈维护高度的单调性
int sum[maxn];//每个下标i接收的能量和
int top;
void solve(){cin >> n;for (int i = 1; i <= n; i++){cin >> h[i] >> v[i];while (top && h[st[top]] < h[i])//比i矮的都出栈sum[i] += v[st[top--]];//栈顶的元素可以给当前的i传递能量sum[st[top]] += v[i];//当前的i给栈顶的元素传递能量st[++top] = i;//入栈}int ans = 0;for (int i = 1; i <= n; i++)ans = max(ans, sum[i]);//更新最值cout << ans << nl;
}int main(){//ios::sync_with_stdio(0);//freopen("in", "r", stdin);int t = 1;//cin>>t;while(t--)solve();return 0;
}
三、双子序列最大和
题目描述
给定一个长度为n的整数序列,要求从中选出两个连续子序列,使得这两个连续子序列的序列和之和最大,最终只需输出最大和。一个连续子序列的和为该子序列中所有数之和。每个连续子序列的最小长度为1,并且两个连续子序列之间至少间隔一个数。
输入格式
第一行是一个整数表示n。
第二行是n个整数表示整数序列。
输出格式
一个数,两个连续子序列的序列和之和。
样例 #1
样例输入 #1
5
83 223 -13 1331 -935
样例输出 #1
1637
样例 #2
样例输入 #2
3
83 223 -13
样例输出 #2
70
提示
对于30%的数据N<=100。
对于60%的数据有N<=10000。
对于100%的数据有N<=1000000。
数据保证运算过程不会超过long long(int64)。
解题思路:
给出一段数列,求出连续的非空的字段,使得和最大。
那么,我们可以令 fif_ifi 表示以 iii 结尾的最大子串,那么,若 fi−1f_{i-1}fi−1 大于 000,则拼在一起比较划算,否则就单独出来.也就是:
fi=max(fi−1+ai,ai)f_{i}=max(f_{i-1}+a_{i},a_{i})fi=max(fi−1+ai,ai)
或者说
fi=max(fi−1,0)+aif_{i}=max(f_{i-1},0)+a_{i}fi=max(fi−1,0)+ai
其核心代码如下:
for(int i=0;i<n;i++){scanf("%d",&x);f[i]=max(f[i-i],0)+a[i];
}
然后我们便可以考虑如何从此题中更深一步得到这道题的解法。
我们仍然可以令 gig_igi 表示以 iii 结尾的两个子串的权值最大值,那么,gig_igi 有两种方法可以得到。
1.由 gi−1g_{i-1}gi−1继承而来,这样与上面是类似的。但与之不同的是,因为要有两个子串,所以前面的我们不能直接舍掉,即不能取 000,所以整个式子就是:
gi=gi−1+aig_{i}=g_{i-1}+a_{i}gi=gi−1+ai
2.由之前的一个子串得来,也就是:
gi=maxj<i−1fj+aig_{i}=\max_{j<i-1}f_j+a_{i}gi=j<i−1maxfj+ai
综合来讲,就是
gi=max(gi−1,maxj<i−1fj)+aig_{i}=\max(g_{i-1},\max_{j<i-1}f_j)+a_{i}gi=max(gi−1,j<i−1maxfj)+ai
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef vector<pair<int,int>> vpii;
#define all(a) a.begin(), a.end()
#define nl '\n'
#define debug() cout << "debug:"
const int inf = 0x3f3f3f3f;
const int maxn = 2e6+5;
//给定一个长度为n的整数序列,要求从中选出两个连续子序列,
//使得这两个连续子序列的序列和之和最大,最终只需输出最大和。
ll one[maxn], two[maxn];
ll ans = -1e18;//记录答案
ll maxx = -1e18;//记录最大的one[]
void solve(){ll n;cin >> n;one[0] = -1e18;for (int i = 1; i <= n; i++){ll x;cin >> x;one[i] = max(one[i - 1], 0ll) + x;maxx = max(maxx, one[i - 1]);two[i] = max(two[i - 1], maxx) + x;if(i>2)//选取两个子串,长度要大于2ans = max(ans, two[i]);}cout << ans << nl;
}
int main(){//ios::sync_with_stdio(0);//freopen("in", "r", stdin);int t = 1;//cin>>t;while(t--)solve();return 0;
}
【单调栈】【概念讲解模板代码】相关推荐
- 单调栈之Next Greater Number
单调栈之Next Greater Number 栈(stack) 是很简单的⼀种数据结构, 先进后出的逻辑顺序, 符合某些问 题的特点, ⽐如说函数调⽤栈. 单调栈实际上就是栈, 只是利⽤了⼀些巧妙的 ...
- 【数据结构】单调栈和单调队列 详解+例题剖析
算法:单调栈和单调队列 一.单调栈和单调队列 二.单调栈例题 1.模板题入门 2.不懂不要急,看这道题 三.单调队列例题 1.入门 2.进阶 一.单调栈和单调队列 单调栈和单调队列与普通的栈,队列不同 ...
- LNSYOJ#204谁是天才【RMQ理解+单调栈】【做题报告】
这是一道RMQ水题,因为本蒟蒻居然一A 题目描述 张大牛:"我是天才!" 大肥熊:"你为什么是天才?" 张大牛:"你随便给我一个单词(大小写字母组成) ...
- 【POJ - 3250 】Bad Hair Day (单调栈)
题干: Some of Farmer John's N cows (1 ≤ N ≤ 80,000) are having a bad hair day! Since each cow is self- ...
- 20191024:单调栈问题的引出
单调栈问题的引出 题目 代码实现 题目 Leetcode习题1124:表现良好的最长时间段 做上题时自己暴力的做法会超时,但是时间不允许继续往下探究,明天完成单调栈的探究. 代码实现 1124题暴力解 ...
- 牛客 小米校招 最大新整数 单调栈
题目描述 有一十进制正整数,移除其中的 K 个数,使剩下的数字是所有可能中最大的. 假设: 字符串的长度一定大于等于 K 字符串不会以 0 开头 输入描述: 一行由正整数组成的数字字符串,和一个正整数 ...
- [jzoj 6305] 最小值 {单调栈}
题目 解题思路 g[i]g[i]g[i]表示1到n已经划分完毕的最大价值和. O(n2)O(n^2)O(n2)方程很容易想到. 我们可以用单调栈来维护. 代码 #include<cstdio&g ...
- 模板_单调栈_AcWing_830. 单调栈_底顶递增栈
模板_单调栈_AcWing_830. 单调栈_底顶递增栈 830. 单调栈 题目 提交记录 讨论 题解 视频讲解 给定一个长度为 NN 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 − ...
- 差异:后缀数组(wzz模板理解),单调栈
因为涉及到对模板的理解,所以就着代码看会好一些. 让那些坚决不颓代码的人受委屈了. 我是对着wzz的板子默写的,可能不完全一样啊. 还有代码注释里都是我个人的理解,不保证正确,但欢迎指正. 可以有选择 ...
最新文章
- 清理SQL Server日志释放文件空间的终极方法
- getopt:命令行选项、参数处理
- vs debug 模式生成的exe 另一台电脑_神秘的 _DEBUG 宏从何处来?
- NanShan企业即时通讯开始写博客
- 应用zip压缩的javascript以及Egret H5游戏实战
- python 员工考勤_用python写的考勤自动打卡程序
- 测试ai模糊软件,“马赛克”视频能被AI软件彻底还原?测试结果出乎意料!
- Arcgis 镶嵌栅格报错999999,且生成x3569458.tif文件
- 通过链接下载google drive文件
- 转载双显示器显示模式介绍
- 深度学习第二次培训(BP神经网络)
- 一个在国内外使用广泛的精密电阻品牌介绍
- 华为mate9安装Fiddler证书
- 创宇蜜罐入驻华为严选商城,与华为云共同构建积极纵深防御体系
- FootStep 6、诺基亚、摩托罗拉、索尼爱立信和三星手机系列命名介绍『整理、了解』
- iphonex 序列号_iPhoneX序列号在哪?苹果iPhoneX序列号怎么看?
- java使用axis2调用webservice接口实例
- powershell导入脚本失败,禁止运行脚本,无法远程连接服务器
- 分布式之数据库和缓存双写一致性方案解析
- 高中关于人工智能方面的课题_《人工智能的发展与应用》课题开题报告