单调栈的基本操作与简单运用

这个坑好大,先写个大概,以后慢慢填 orz

目录

  • 周任务:单调栈的学习与应用
    • 定义
    • 例题
    • 性质
    • 优点
    • 简单的操作与维护
    • 具体应用
    • 与单调队列的区别

周任务:单调栈的学习与应用

首先:单调栈是一种特殊的栈,特殊之处在于栈内的元素都保持一个单调性。

定义

“单调” “栈”,从字面意思来看,跟单调有关,跟栈有关。单调的含义学了数学的都知道指的是函数的单调性,也就是一直增/减,例如1,2,3,4就是一个单调递增的数列,单调栈中的元素就有这样的性质。

例题

  • POJ2559
  • POJ3494
  • 牛客19暑期多校第一场A题
  • 牛客19暑期多校第二场H题
  • 牛客19暑期多校第八场A题

性质

  • 栈的基本性质
  • 栈内的元素必须满足单调性,可以是单调递增,也可以是单调递减,如果新入栈的元素破坏了已有的单调性,就弹出栈内元素知道满足单调性

优点

  • 时间复杂度为O(n),可以线性求出数组中某个数的左边或者右边第一个比它大或者比它小的元素
  • 理解起来很容易,上手比较快,每个数字只进栈并处理一次,但是解决问题的核心在于如何处理进栈元素

简单的操作与维护

以单调递增栈为例,比如数组:2 7 1(图很丑麻烦见谅)
在这里插入图片描述
简易代码如下:

while(s.empty()) s.pop(); //先清空栈
a[n]=-1;
for(i=0;i<=n;i++){
	if(s.empty()||a[j]>=a[s.top()]){ //如果栈为空或入栈元素大于等于栈顶元素,则入栈 
		s.push(j);
	}
	else{
		while(!s.empty()&&a[j]<a[s.top()]){ //如果栈非空并且入栈元素小于栈顶元素,则将栈顶元素出栈 
			top=s.top();	
			s.pop();
		}
		s.push(top); //将最后一次出栈的栈顶元素延伸并入栈 
		a[top]=a[j]; //修改其对应的值 
	}	 
}

以上是用STL中的栈实现的,用模拟栈也是可以的,单调栈不仅仅可以存数组元素,也可以存放数组下标而栈内元素并不单调(即下标单调),这就需要对单调栈的灵活运用了

具体应用

POJ2559
这个题目基本意思是求出直方图中最大的矩形面积,思路为建立一个单调递增栈,所有元素逐个进栈,出栈时更新最大矩形面积。

输入样例:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出样例:
8
4000

栈空,2进栈,当前最大面积为2。1准备进栈,此时1比2小,弹栈操作,2出栈,1进栈,更新最大面积仍为2,栈内元素为1,剩下高为1,宽为2的矩形;4进栈,5进栈,1进栈进栈前为满足单调递增,5出栈时更新最大矩形面积为2*4 = 8,紧接着4也出栈,矩形面积为4(<8,不必更新),故最大矩形面积仍为8,栈内元素为1,1,剩下高为1,宽为5的矩形;接着两个3依次进栈,无弹栈操作,直到没有元素进栈。弹出余下元素。最后答案为8。
代码如下:

#include
#include
#include
#define N 100005
using namespace std;

int main(){
	int n;
	long long a[N],b[N];
    while(scanf("%d", &n) != EOF && n){
        long long ans = 0;
        for(int i = 1;i <= n;++i) {
        	scanf("%d", &a[i]);	
		}
        stack<int> s;
        s.push(n+1);
		a[n+1] = -1;
        for(int i = n;i>0;--i){
            while(a[s.top()] >= a[i])
                s.pop();
            b[i] = s.top();
            s.push(i);
        }
        while(!s.empty()){
			s.pop();
		} 
        s.push(0);
		a[0] = -1;
        for(int i = 1;i <= n;++i){
            while(a[s.top()] >= a[i])
                s.pop();
            ans = max(ans, a[i]*(b[i]-s.top()-1));
            s.push(i);
        }
        cout << ans << endl;
    }
    return 0;
}

POJ3494:
这题大致意思是找到最大的全一矩阵,属于二维的单调栈问题,需要先进行预处理,若当前行为1,则当前行在上一行的基础上累加1,否则为0,每行都用单调递减栈进行处理,方法类似于2559,具体代码如下:

#include
#include
#include
#include
using namespace std;
 
int main(){
	int i,j,m,n,x,top,tmp,ans,h[2020],a[2020];
	stack<int> s;
	while(~scanf("%d%d",&m,&n)){
		ans=0;
		memset(h,0,sizeof(h));
		for(i=0;i<m;i++){
			for(j=1;j<=n;j++){
				scanf("%d",&x);
				if(x==1){
					h[j]++; 
				}
				else h[j]=0; 
				a[j]=h[j]; 
			}
			a[n+1]=-1; 
			for(j=1;j<=n+1;j++){
				if(s.empty()||a[j]>=a[s.top()]){
					s.push(j);
				}
				else{
					while(!s.empty()&&a[j]<a[s.top()]){
						top=s.top();
						s.pop();
						tmp=(j-top)*a[top]; 
						if(tmp>ans) ans=tmp;
					}
					s.push(top);
					a[top]=a[j];
				}
			}
		}
		cout << ans << endl;
	}
	return 0;
}

牛客第一场A题:
从左到右遍历一遍,记录下第一个单调栈结果不同的地方,该位置前一个位置就是结果。

#include
#define MAX 100000
using namespace std;
int main(){

	int n;

	while(~scanf("%d",&n)){
			int a[MAX],b[MAX];
		stack<int> s1,s2;
		for(int i=0;i<n;i++){
			scanf("%d",&a[i]);
		}
		for(int i=0;i<n;i++){
			scanf("%d",&b[i]);
		}		
		int num=1;
		s1.push(a[0]);
		s2.push(b[0]);
		while(1){
			if(num==n){
				printf("%d\n",num);
				break;
			}
			while(1){
				if(!s1.empty()){
					if(s1.top()>a[num]){
						s1.pop();
					}
					else{
						s1.push(a[num]);
						break;
					}
				}
				else {
				s1.push(a[num]);break;	
				}
			}
		
			while(1){
				if(!s2.empty()){
					if(s2.top()>b[num]){
						s2.pop();
					}
					else{
						s2.push(b[num]);
						break;
					}
				}
				else {
					s2.push(b[num]);break;
				}
			}
			if(s1.size()==s2.size()){
				num++;
			}
			else{
				printf("%d\n",num);
				break;
			}
		}
	}
	return 0;
}

与单调队列的区别

与单调栈的定义相似,单调队列就是队内元素符合单调性质的队列,而且队首队尾都可以进行出队操作,只有队尾可以进行入队操作

你可能感兴趣的:(ACM)