单调栈(洛谷模板+例题)

问题引入

  • 给定一个序列,求序列中每一个位置的f(i),f(i)为第i个元素右边第一个大于ai的元素的下标;

分析

  • 从一个元素出发向右看,若出现单调不增部分i~j,显然 i~j不会影响i左边的f值,整个问题的求解与单调性有关。
  • 单调栈:字面意思,具有元素单调性的栈;在这个题中我们维护一个自栈顶栈底单调递的栈,从右向左扫一遍,若发现当前元素大于等于栈顶元素,就不断的删除栈顶,最后栈顶元素(如果有)的下标就是当前元素的f值,然后再将当前元素加入栈。单调栈的精髓在于每个元素只需入栈一次,可以实现O(1)得到答案

洛谷模板

#include<iostream>
#include<cstdio>
int n,a[3000005],sta[3000005],tp,ans[3000005];//存下标
using namespace std;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=n;i>=1;i--)
	{
		while(tp&&a[sta[tp]]<=a[i]) tp--;
		ans[i]=tp?sta[tp]:0;
		sta[++tp]=i;
	}
	cout<<ans[1];
	for(int i=2;i<=n;i++)
	cout<<' '<<ans[i];
	return 0;
}

SP1805 HISTOGRA - Largest Rectangle in a Histogram

  • 一道矩形最大覆盖问题,比较经典巧妙。
#include<iostream>
#include<cstdio>
#include<cmath>
int n,tp;
long long h[100010],maxn,f[100010],len,sta[100010];
using namespace std;
int main()
{
    while(scanf("%d",&n)&&n)
    {
        tp=len=0;
        for(int i=1; i<=n; i++)
        {
            scanf("%lld",&h[i]);
            f[i]=0;
        }
        for(int i=1; i<=n; i++)
        {
            if(sta[tp]>h[i])
            {
                len=0;
                while(tp&&sta[tp]>h[i])
                {
                    len+=f[tp];
                    maxn=max(maxn,sta[tp--]*len);
                }
                sta[++tp]=h[i];
                f[tp]=len+1;
            }
            else
            {
                sta[++tp]=h[i];
                f[tp]=1;
            }
        }
        len=0;
        while(tp)
        {
            len+=f[tp];
            maxn=max(maxn,sta[tp--]*len);
        }
        cout<<maxn<<endl;
        maxn=0;
    }
    return 0;
}

P1823 [COI2007] Patrik 音乐会的等待

  • 与模板有些像,统计答案时需要用到元素重复数。
#include<iostream>
#include<cstdio>
using namespace std;
long long n,a[500005],sta[500005],tp,ans=-1,num[500005];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		if(tp&&a[i]>sta[tp])
		{
			while(tp&&a[i]>sta[tp])
			{
				tp--;
				ans++;
			}
			if(tp&&a[i]==sta[tp])
			ans+=(num[tp]+(tp>num[tp]?1:0));
			else ans+=(tp?1:0);
		}
		else
		ans+=((a[i]==sta[tp])?(num[tp]+(tp>num[tp]?1:0)):1);
		sta[++tp]=a[i];
		num[tp]=1;
		if(a[i]==sta[tp-1]) num[tp]=num[tp-1]+1;
	}
	cout<<ans;
	return 0;
} 

P2947 [USACO09MAR]Look Up S

  • 模板题,用了两种实现方法,单调栈倒着扫,单调队列正着扫。
#include<iostream>
#include<cstdio>
using namespace std;
int n,h[100005],que[100005],tp,sta[100005],ans[100005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&h[i]);
	for(int i=1;i<=n;i++)
	{
		while(tp&&h[i]>h[sta[tp]])
			ans[sta[tp--]]=i;
		sta[++tp]=i;
	}
//	for(int i=n;i>=1;i--) 单调栈 
//	{
//		while(tp&&h[sta[tp]]<=h[i]) tp--;
//		ans[i]=sta[tp];
//		sta[++tp]=i;
//	}
	for(int i=1;i<=n;i++)
	cout<<ans[i]<<endl;
	return 0;
}
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单调栈是一种特殊的栈结构,它可以快速找到栈中的最大值或最小值。单调栈常用于解决一些数组、字符串中的问题,例如求下一个更大的元素、求前一个更大的元素等等。 下面是单调栈的代码模板: ``` stack<int> mono_stack; for (int i = 0; i < n; i++) { while (!mono_stack.empty() && mono_stack.top() > x[i]) { mono_stack.pop(); } mono_stack.push(x[i]); } ``` 其中,`x` 是一个数组,`n` 是数组的长度,`mono_stack` 是单调栈。这段代码的意思是:遍历数组 `x`,对于每个元素 `x[i]`,将其压入栈中,然后不断弹出栈顶元素,直到栈为空或者栈顶元素不大于 `x[i]`。这样,最终单调栈中的元素就是一个单调递增的序列。 如果要求下一个更大的元素,可以在遍历数组时,记录每个元素的下标,然后在弹出栈顶元素时,更新下标对应的答案。代码如下: ``` stack<pair<int, int>> mono_stack; for (int i = 0; i < n; i++) { while (!mono_stack.empty() && mono_stack.top().first < x[i]) { ans[mono_stack.top().second] = x[i]; mono_stack.pop(); } mono_stack.push(make_pair(x[i], i)); } ``` 其中,`ans` 是答案数组,`ans[i]` 表示第 `i` 个元素的下一个更大的元素。在弹出栈顶元素时,更新对应下标的答案即可。 如果要求前一个更大的元素,可以将数组逆序遍历,然后按照同样的方式求解。代码如下: ``` stack<pair<int, int>> mono_stack; for (int i = n - 1; i >= 0; i--) { while (!mono_stack.empty() && mono_stack.top().first < x[i]) { ans[mono_stack.top().second] = x[i]; mono_stack.pop(); } mono_stack.push(make_pair(x[i], i)); } ``` 以上就是单调栈的代码模板,可以根据具体问题进行修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈希表扁豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值