单调栈

单调栈

:栈是一种“后进先出”的线性数据结构。栈只有一端能够进出元素,我们一般称这一端为栈顶,另一端为栈底。添加或删除栈中元素时,我们只能将其插入到栈顶(进栈),或者把栈顶元素从栈取出(出栈)。

什么是单调栈?

  单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈和单调递减栈。
  单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
  单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大

模拟实现一个递增单调栈:

  现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空或入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减的栈反之。
  10入栈时,栈为空,直接入栈,栈内元素为10。
  3入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。
  7入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。
  4入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。
  12入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。

单调栈的伪代码:
stack<int> st;
for (遍历这个数组)
{
	if (栈空 || 栈顶元素大于等于当前比较元素)
	{
		入栈;
	}
	else
	{
		while (栈不为空 && 栈顶元素小于当前元素)
		{
			栈顶元素出栈;
			更新结果;
		}
		当前数据入栈;
	}
}

接下来用两道简单题来介绍下单调栈的具体应用。

P1901 发射站

题目描述

  某地有 N 个能量发射站排成一行,每个发射站 i 都有不相同的高度 Hi ,并能向两边(两端的发射站只能向一边)同时发射能量值为 Vi 的能量,发出的能量只被两边最近的且比它高的发射站接收。显然,每个发射站发来的能量有可能被 0 或 1 或 2 个其他发射站所接受。
  请计算出接收最多能量的发射站接收的能量是多少。

输入格式

  第 1 行一个整数 N。
  第 2 到 N + 1 行,第 i + 1 行有两个整数 Hi 和 Vi,表示第 i 个人发射站的高度和发射的能量值。

输出格式

  输出仅一行,表示接收最多能量的发射站接收到的能量值。答案不超过 32 位带符号整数的表示范围。

输入输出样例
输入

3
4 2
3 5
6 10

输出

7

洛谷:P1901[发射站]

说明/提示

分析:

  这道题属于单调栈,我们维护栈中的发射站高度的单调性。栈中储存发射站的标号。我们将接收能量的过程分成两个阶段:
  1.入栈时,由于栈是具有单调性的,如果栈顶的元素没有新加进来的元素高,那么他肯定就不能给后面的元素传输能量了,我们退掉这个元素,将新的元素加进来即可。
  2.新的元素加进来以后,会对他在栈中下面那个元素传输能量,也就是离他最近还高于他的那个。
算法流程:
  for{
    读入新元素
    while(栈非空 && 新元素高度大于栈顶元素高度)新元素能量加上栈顶元素能量,退栈;
    若栈不为空,且新元素高度小于栈顶元素高度(排除高度相等的情况,虽然这道题不会卡高度相等这个点),则栈顶元素能量加上新元素能量(对应分析的第二种情况);
    将新元素加入栈中;
  }
  遍历传输能量的数组,找到max输出即可。

代码如下:

#include <bits/stdc++.h>
using namespace std;
int a[1000005], h[1000005], v[1000005], ans[1000005], maxx;
stack <int> s;
int main(){
	int n, i; 
	scanf("%d", &n);
	for(i = 0; i < n; i++){
		scanf("%d%d", &h[i], &v[i]);
		while(!s.empty() && h[s.top()] < h[i]){
			ans[i] += v[s.top()];
			s.pop();
		}
		if(!s.empty() && h[s.top()] > h[i]) ans[s.top()] += v[i];
		s.push(i);
	}
	for(i = 0; i < n; i++){
		maxx = max(ans[i], maxx);
	}
	printf("%d", maxx);
	return 0;
} 

P1823 [COI2007] Patrik 音乐会的等待

题目描述

  n 个人正在排队进入一个音乐会。人们等得很无聊,于是他们开始转来转去,想在队伍里寻找自己的熟人。队列中任意两个人 a 和 b,如果他们是相邻或他们之间没有人比 a 或 b 高,那么他们是可以互相看得见的。
  写一个程序计算出有多少对人可以互相看见。

输入格式

  输入的第一行包含一个整数 n,表示队伍中共有 n 个人。
  接下来的 n 行中,每行包含一个整数,表示人的高度,以毫微米(等于 10−9米)为单位,这些高度分别表示队伍中人的身高。

输出格式

  输出仅有一行,包含一个数 s,表示队伍中共有 s 对人可以互相看见。

输入输出样例
输入

7
2
4
1
2
2
5
1

输出

10

洛谷:P1823[COI2007]Patrik音乐会的等待

说明/提示

对于全部的测试点,保证 1≤ 每个人的高度 < 231,1 ≤ n ≤ 5 ×105

分析:

  这道题还是属于单调栈的题,给出一个数组,要找出所有能相互看见(两个人之间没有比两个人任意一个人高的人)总共有多少对,本来这道题直接用设一个栈,从左往右读入数组元素比栈顶元素小,则进栈,并且sum++(此时这个元素只有跟栈顶元素是能“互相看到的”,因为栈顶下面的元素都比栈顶大,会被栈顶的“人”档住);如果读入的数比栈顶元素大,则sum++之后栈顶的元素弹出(栈顶的元素比栈顶下面的数小,又比新读入的数小,被两“高”的夹在中间,后续没什么用了),再把新元素压进栈。
  我之前是这样做的,然后wa了,其实忽略了如果新读入的元素与栈顶的元素相等时的情况,此时栈顶不能出栈(如果下一次读入一个比栈顶的数大的元素时,栈顶的元素还是能与其“互相看到的”),但还要计算栈内与读入的元素相等数还有几个,这个过程很复杂,这就是这道题为何是“提高+/省选-”的原因。
  所以需要定义node来记录,h表示读入元素的数值,v初始化为1(表示个数),在元素不断进栈的过程中,如果遇到相等的数时,应该把其弹出,修改其v的值(加一),相当于这里有v个相同的h数在这里(因为sum都是利用a[i].v进行累加的),之后再把node压进栈(注意:压进栈的node的v值应加一),格式为:s.push((node) {a[i].h, u + 1}); u是储存栈顶元素v值的中间变量。

代码如下:

#include <bits/stdc++.h>
using namespace std;
struct node{
	int h;
	int v;
}a[500005];
stack <node> s;
long long sum;
int main(){
	int n, i, u;
	scanf("%d", &n);
	for(i = 0; i < n; i++){
		scanf("%d", &a[i].h);
		a[i].v = 1;
		while(!s.empty() && s.top().h < a[i].h){
			sum += s.top().v;
			s.pop();
		}
		if(!s.empty() && s.top().h == a[i].h){
			sum += s.top().v;
			u = s.top().v;
			s.pop();
			if(!s.empty()) sum ++;
			s.push((node) {a[i].h, u + 1});
		}
		else{
			if(!s.empty()) sum ++;
			s.push(a[i]);
		}
	}
	printf("%lld", sum);
	return 0;
}
单调栈是一种常用的数据结构,用于解决一类特定的问题,其中最常见的问题是找到数组中每个元素的下一个更大或更小的元素。在Codeforces编程竞赛中,单调栈经常被用于解决一些与数组相关的问题。 下面是单调栈的一般思路: 1. 创建一个空。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与顶元素进行比较。 - 如果当前元素小于等于顶元素,则将当前元素入。 - 如果当前元素大于顶元素,则将顶元素弹出,并将当前元素入。 4. 重复步骤3,直到遍历完所有元素。 这样,最后剩下的中元素就是没有下一个更大或更小元素的元素。在使用单调栈求解具体问题时,我们可以根据需要进行一些特定的操作。 例如,如果要找到一个数组中每个元素的下一个更大的元素,可以使用单调递减。具体操作如下: 1. 创建一个空和一个空结果数组。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与顶元素进行比较。 - 如果当前元素小于等于顶元素,则将当前元素入。 - 如果当前元素大于顶元素,则将顶元素弹出,并将其在结果数组中的位置记录为当前元素的下一个更大元素的索引。 4. 将当前元素入。 5. 重复步骤3和4,直到遍历完所有元素。 6. 结果数组中没有下一个更大元素的位置,可以设置为-1。 以下是一个使用单调递减求解下一个更大元素问题的示例代码: ```cpp #include <iostream> #include <stack> #include <vector> std::vector<int> nextGreaterElement(std::vector<int>& nums) { int n = nums.size(); std::vector<int> result(n, -1); std::stack<int> stack; for (int i = 0; i < n; i++) { while (!stack.empty() && nums[i] > nums[stack.top()]) { result[stack.top()] = i; stack.pop(); } stack.push(i); } return result; } int main() { std::vector<int> nums = {1,3, 2, 4, 5, 1}; std::vector<int> result = nextGreaterElement(nums); for (int i = 0; i < result.size(); i++) { std::cout << "Next greater element for " << nums[i] << ": "; if (result[i] != -1) { std::cout << nums[result[i]]; } else { std::cout << "None"; } std::cout << std::endl; } return 0; } ``` 以上代码将输出: ``` Next greater element for 1: 3 Next greater element for 3: 4 Next greater element for 2: 4 Next greater element for 4: 5 Next greater element for 5: None Next greater element for 1: None ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值