「数据结构详解·十一」单调栈


鸽了大半年,我回来了(


1. 单调栈的概念及实现

首先你要学过,没学过栈的可以看 「数据结构详解·三」栈
单调栈(Monotone Stack),即元素满足单调性的栈
我们直接以 P5788 【模板】单调栈 为例。
如果我们直接枚举每个数,那么显然时间复杂度是 O ( n 2 ) O(n^2) O(n2),并不能通过。
于是我们要想办法优化。
首先观察样例,1 4 2 3 5
我们会发现,对于 a i a_i ai,我们只关心 a i + 1 ∼ n a_{i+1\sim n} ai+1n 的数值的情况,前面都不用管。
所以我们可以开一个单调栈,其中的元素满足单调不增。这样,对于每个栈中的元素,在它头上都是比它小的,等到有比它大的元素出现的时候,它就会被弹出去,确定答案。
手动模拟一下:

  • i = 1 i=1 i=1 1 1 1 入栈;
  • i = 2 i=2 i=2,此时发现 a 2 a_2 a2 大于栈顶的 a 1 a_1 a1,因此我们可以直接确定 f ( 1 ) = 2 f(1)=2 f(1)=2 1 1 1 出栈, 2 2 2 入栈;
  • i = 3 i=3 i=3 3 3 3 入栈;
  • i = 4 i=4 i=4,此时发现 a 4 a_4 a4 大于栈顶的 a 3 a_3 a3,因此我们可以直接确定 f ( 3 ) = 4 f(3)=4 f(3)=4 3 3 3 出栈, 4 4 4 入栈;
  • i = 5 i=5 i=5,此时发现 a 5 a_5 a5 大于栈顶的 a 4 a_4 a4,因此我们可以直接确定 f ( 4 ) = 5 f(4)=5 f(4)=5 4 4 4 出栈;发现 a 5 a_5 a5 大于栈顶的 a 2 a_2 a2,因此我们可以直接确定 f ( 2 ) = 5 f(2)=5 f(2)=5 2 2 2 出栈, 5 5 5 入栈。

于是最后答案就确定完毕了。
可以发现我们遍历的时间复杂度是 O ( n ) O(n) O(n),而每个元素至多入栈一次、出栈一次,因此最终的时间复杂度是 O ( n ) O(n) O(n),可以通过。

#include<bits/stdc++.h>
using namespace std;

stack<int>s;
int n,a[3000005],f[3000005];

int main()
{
	ios::sync_with_stdio(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=n;i++)
	{
		while(!s.empty()&&a[s.top()]<a[i])//栈顶的元素遇到的第一个比它大的元素
		{
			f[s.top()]=i;//确定答案
			s.pop();
		}
		s.push(i);
	}
	for(int i=1;i<=n;i++) cout<<f[i]<<' ';
 	return 0;
}

2. 例题详解

2-1. P1823 [COI2007] Patrik 音乐会的等待

我们考虑开一个单调递减的单调栈,这样栈内的一个人当遇到比自己高的人时就可以计数并弹出。
但是因为有身高相同的情况,那么我们只要栈内开一个 pair,记身高与对应的人数即可。

#include<bits/stdc++.h>
#define int long long 
using namespace std;

stack<pair<int,int>>s;
int n,ans;

signed main()
{
	scanf("%d",&n);
	while(n--)
	{
		int x;
		scanf("%d",&x);
		pair<int,int>p={x,1};
		while(!s.empty()&&s.top().first<=x)
		{
			ans+=s.top().second;
			if(s.top().first==x) p.second+=s.top().second;
			s.pop();
		}
		if(!s.empty()) ans++;
		s.push(p);
	}
	cout<<ans;
	return 0;
}

3. 巩固练习

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值