单调栈的应用:
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);
}
}
题意:给定由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;
}
题意:给定一段数组,求一段区间的和乘以区间的最小值最大。
思路:单调栈维护,用前缀和数组维护区间和。
#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);
}
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);
}