单调栈

单调栈的应用:

1.最基础的应用就是给定一组数,针对每个数,寻找它和它右边第一个比它大的数之间有多少个数。

2.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。

3.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。

P2866 [USACO06NOV]糟糕的一天Bad Hair Day

题意:一群牛站成一排,每头牛可以看到他右边第一个比他大的牛之间的牛,问每头牛可以看到的牛的总和。

思路:单调栈维护,当当前元素小于栈顶元素时,该元素一定能被栈顶元素看到,否则栈里面比它小的全部出栈,因为前面的都看不到了,该元素入栈。其实就是维护一个能够不被当前元素挡住的元素的个数,而我们求得就是这个的和。

以下是不同的两种做法。

#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
int a[80005];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	long long sum=0;
	stack<int>s;
	for(int i=0;i<n;i++){
		while(!s.empty()&&s.top()<=a[i])
			s.pop();
		sum+=s.size();
		s.push(a[i]);
	}
	printf("%lld\n",sum);
}
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
 
int main()
{
	int i,n,top,a[80010]; //top指向栈顶元素 
	LL ans; //存储最终结果 
	stack<int> st; //st为栈,每次入栈的是每头牛的位置 
	while(~scanf("%d",&n))
	{
		while(!st.empty()) st.pop(); //清空栈 
		for(i=0;i<n;i++)
			scanf("%d",&a[i]);
		a[n]=inf; //将最后一个元素设为最大值 
		ans=0;
		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();     //栈顶元素出栈 
					//这时候也就找到了第一个比栈顶元素大的元素 
					//计算这之间牛的个数,为下标之差-1 
					ans+=(i-top-1);	
				}
				st.push(i); //当所有破坏栈的单调性的元素都出栈后,将当前元素入栈 
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

Largest Rectangle in a Histogram

题意:在一段区间上有长相等,高低不等的一些长方形,求最大的子长方形面积。

思路:用单调栈维护区间最低值,如果有大于等于栈顶的入栈,否则出栈,合并区间。

#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
ll a[100005];
int main(){
	int n;
	while(~scanf("%d",&n)&&n){
		for(int i=1;i<=n;i++)
			scanf("%lld",&a[i]);
		a[n+1]=-1;
		stack<int>s;
		ll ans=0;
		for(int i=1;i<=n+1;i++){
			if(s.empty()||a[i]>=a[s.top()])
				s.push(i);
			else{
				int top;
				ll t;
				while(!s.empty()&&a[i]<a[s.top()]){
					top=s.top();
					s.pop();
					t=(i-top)*a[top];
					ans=max(ans,t);
				}
				s.push(top);
				a[top]=a[i];
			}
		}
		printf("%lld\n",ans);
	}
}

Largest Submatrix of All 1’s

题意:给定由0和1组成的矩阵,求其中的最大矩形面积。

思路:预处理一下,每一行可以和上一行对应位置的1合并,那么我们可以由1维推广到二维求上面的那个问题。

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

Feel Good

题意:给定一段数组,求一段区间的和乘以区间的最小值最大。

思路:单调栈维护,用前缀和数组维护区间和。

#include<cstdio>
#include<stack>
using namespace std;
typedef long long ll;
ll sum[100005];
int a[100005];
int main(){
	int n;
	scanf("%d",&n);
	sum[0]=0;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	a[n+1]=-1;
	ll ans=-1;
	int l,r;
	stack<int>s;
	for(int i=1;i<=n+1;i++){
		if(s.empty()||a[i]>=a[s.top()])
			s.push(i);
		else{
			int top;
			ll t;
			while(!s.empty()&&a[i]<a[s.top()]){
				top=s.top();
				s.pop();
				t=1ll*(sum[i-1]-sum[top-1])*a[top];
				if(t>ans){
					ans=t;
					l=top;
					r=i-1;
				}
			}
			s.push(top);
			a[top]=a[i];
		}
	}
	printf("%lld\n%d %d\n",ans,l,r);	
}

Building

Special Segments of Permutation

题意:一段区间,区间最大值等于区间最左端的数加区间最右端的数,求整个区间有多少个这样的区间。

思路:用单调栈维护i为最大值的区间,再遍历区间是否符合要求。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
int l[maxn],r[maxn],a[maxn],id[maxn],n,ans;
void qu(int l,int r,int l1,int r1,int s){
	for(int i=l;i<=r;i++){
		int k=s-a[i];
		if(k>=1&&k<=n&&l1<=id[k]&&id[k]<=r1)
			ans++;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		id[a[i]]=i;
	}
	stack<int>s;
	for(int i=1;i<=n;i++){
		while(s.size()&&a[i]>a[s.top()])
			s.pop();
		if(!s.size())
			l[i]=0;
		else l[i]=s.top();
		s.push(i);
	}
	while(!s.empty())
		s.pop();
	for(int i=n;i>=1;i--){
		while(s.size()&&a[i]>a[s.top()])
			s.pop();
		if(!s.size())
			r[i]=n+1;
		else r[i]=s.top();
		s.push(i);
	}
	ans=0;
	for(int i=1;i<=n;i++){
		int ll=i-l[i]-1;
		int rr=r[i]-i-1;
		if(!ll||!rr)
			continue;
		if(ll<rr)
			qu(l[i]+1,i-1,i+1,r[i],a[i]);
		else qu(i+1,r[i],l[i]+1,i-1,a[i]);
	}
	printf("%d\n",ans);
} 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值