单调栈总结

单调栈归纳

根据博客[单调栈原理及应用 详解 附各种类型的题目练习 - zuzhiang的博客 - CSDN博客](https://blog.csdn.net/zuzhiang/article/details/78134247)及其他博客总结。(详情看原博客,比我的详细多了)

用处:

  1. 最基础的应用就是给定一组数,针对每个数,寻找它和它右边第一个比它大的数之间有多少个数。
  2. 给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。
  3. 给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。
用处1对应题目:

POJ 3250 – Bad Hair Day
题意:有一群牛站成一排,每头牛都是面朝右的,每头牛可以看到他右边身高比他小的牛。给出每头牛的身高,要求每头牛能看到的牛的总数。
思路:这也就是应用1所说的求每个数和它右边第一个比它大的数之间的数的个数,分别求出后相加即可。朴素的做法是双重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。
坑点:结果范围,不能只有int ,而需要long long int。

#include<iostream>
#include<stack>
using namespace std;
int main()
{
    int n;
    cin >> n;
    int q[n],sum[n];
    for(int i = 0;i < n;i++)
    {
        cin >> q[i];
    }
    stack<int> s;
    for(int i = 0;i < n;i++)
    {
        if(s.empty() || q[s.top()] > q[i])
        {
            s.push(i);
        }
        else
        {
            int tem = s.top();
            sum[tem] = i - tem - 1;
            s.pop();
            i--;
        }
    }
    while(!s.empty())
    {
        int tem = s.top();
        sum[tem] = n - tem - 1;
        s.pop();
    }
    long long int ans = 0;//坑点:结果范围。
    for(int i = 0;i < n;i++)
    {
        ans += sum[i];
    }
    cout << ans << endl;
}
用处2对应题目:

POJ 2559 – Largest Rectangle in a Histogram
题意:有N个矩形,宽度都为1,给出N个矩形的高度,求由这N个矩形组成的图形包含的最大的矩形面积。
思路:可以转化为求区间最小值乘以区间长度的最大值。普通的思路是两重循环遍历,时间复杂度为O(n^2),用单调栈为O(n)。
解法(frompoj 2559(单调栈) - 越看越喜欢啊 - CSDN博客):
把高度看成一个序列,当高度递增的时候,答案在这个递增序列往回寻找。例如,
1,2,3,4,5,6;更新答案的时候有这么几个选择(6x1),(5x2),(4x3),(3x4),(2x5),(1x6).(高x宽)
当高度出现下降的时候,这时左端比它高的矩形有一部分是无用的。
例如,1,2,3,4,5,6,4;可以看成1,2,3,4,4,4,4;因为我们采取往左更新的方式,5,6这两个大于4的部分是无法被使用上的(往右更新也一样)。所以我们可以把5,6,4这三个矩形合并成一个高度为4,宽度为3的矩形,使得整个序列单调递增.
代码(fromPOJ 2559 &&HDU 1506 Largest Rectangle in a Histogram && 51nod 1102 面积最大的矩形 单调栈的应用 - zuzhiang的博客 - CSDN博客 ):

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
using namespace std;
typedef long long LL;
 
int main()
{
	int i,n,top; //top指向栈顶 
	stack<int> st; //栈用于保存矩形的编号,即位置 
	LL tmp,ans,a[100010]; //tmp为临时变量,记录面积的值,ans为结果,记录最大面积值 
	while(~scanf("%d",&n)&&n)
	{
		for(i=0;i<n;i++)![IMG_20181212_141702.jpg](attachments\6e2e9506.jpg)
			scanf("%lld",&a[i]);
		ans=0;
		a[n]=-1; //最后一个元素设为最小值,以最后清空栈 
		for(i=0;i<=n;i++)
		{
			if(st.empty()||a[i]>=a[st.top()])
			{ //如果栈为空或入栈元素大于等于栈顶元素 ,则入栈 
				st.push(i);
			}
			else
			{
				while(!st.empty()&&a[i]<a[st.top()])
				{ //如果栈非空且入栈元素小于栈顶元素,则将栈顶元素出栈 
					top=st.top();
					st.pop();
					tmp=(i-top)*a[top]; //在出栈过程中计算面积值 
					if(tmp>ans) ans=tmp; //更新面积最大值 
				}
				st.push(top); //只将可以延伸到的最左端的位置入栈
				a[top]=a[i];  //并修改该位置的值 
        /**此处实际也是高度大的矩形合并为一个高度相同长度大于1的矩形
        */
			}			
		}
		printf("%lld\n",ans);
	}
	return 0;
}

POJ 3494 – Largest Submatrix of All 1’s
题目大意:求仅由0,1组成的矩阵中,全部由1组成的子矩阵的最大面积。
参考前辈博客:POJ 3494 Largest Submatrix of All 1’s 单调栈应用 图解+代码详解 - zuzhiang的博客 - CSDN博客

用处3对应题目:

POJ 2796 – Feel Good
题意:给出一个序列,求出一个子序列,使得这个序列中的最小值乘以这个序列的和的值最大。
思路:直接用单调栈解决即可,由于维护单调栈的过程中会改变原数组的值,所以需要加一个sum数组保存前缀和,也方便计算区间元素和。
大佬代码;

#include<stdio.h>
#include<iostream>
#include<stack>
using namespace std;
typedef long long LL;
 
int main()
{
	int i,n,pos1,pos2; //pos1和pos2记录区间的开始和结束位置 
	//tmp为临时变量,记录区间内的和;top指向栈顶元素;ans为结果;sum为前缀和 
	LL tmp,top,ans,a[100010],sum[100010];
	stack<int> st; //单调栈,记录元素位置 
	while(~scanf("%d",&n))
	{
		while(!st.empty()) st.pop(); //清空栈 
		sum[0]=0;
		for(i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
			sum[i]=sum[i-1]+a[i]; //计算前缀和 
		}			
		a[n+1]=-1; //将最后一个设为最小值,以最后让栈内元素全部出栈 
		ans=0;
		for(i=1;i<=n+1;i++)
		{
			if(st.empty()||a[i]>=a[st.top()])
			{ //如果栈为空或入栈元素大于等于栈顶元素,则入栈 
				st.push(i);
			}
			else 
			{
				while(!st.empty()&&a[i]<a[st.top()])
				{ //如果栈非空并且入栈元素小于栈顶元素,则将栈顶元素出栈 
					top=st.top();
					st.pop();					
					tmp=sum[i-1]-sum[top-1]; //计算区间内元素和 
					tmp*=a[top]; //计算结果 
          /*
              $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
              a[top]始终为a[10010]在区间[st.top(),  i )上的最小值。
              %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
          */
					if(tmp>=ans) 
					{ //更新最大值并记录位置 
						ans=tmp;
						pos1=top;
						pos2=i;
					}
				}
				st.push(top); //将最后一次出栈的栈顶元素入栈 
				a[top]=a[i]; //将其向左向右延伸并更新对应的值 
			}
		}
		printf("%lld\n",ans);
		printf("%d %d\n",pos1,pos2-1);
	}
	return 0;
}

单调栈的实质:

对于非递减单调栈:
设原序列为 l[n],在此基础上修改后的为a[n],栈为s,假设对栈可随机访问,则对于栈内任一元素s[m]和s[m+1],a[s[m]]为原序列 l 在区间[ s[m] , s[m+1] ) 内的最小值。

暂存

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值