单调栈的应用
Posted 桂月二四
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单调栈的应用相关的知识,希望对你有一定的参考价值。
单调栈,顾名思义,就是存放的元素是单调的栈。
单调栈主要是用来计算某个数左边/右边第一个比它小/大的数,基本上所有单调栈的题都是用到单调栈的这个功能。
下面以计算某个数左边第一个比它小的数为例,如果是暴力,对于一个数而言,复杂度是可以接受的,但是倘若要对一个数组所有的数字都计算左边第一个比他小的数呢?
于是要利用单调栈。观察一串序列 3 9 7 4 5 我们要计算每个数左边第一个比他小的数(没有则输出-1) ,显然,第一个数的答案是-1,将3压入单调栈。
计算数字9 时,我们发现单调栈的栈顶元素比9小,于是9的答案是3 ,后将9压入栈顶。
计算数字7时,我们发现单调栈栈顶的元素是9,大于7,不是我们需要的数。这个时候,我们可以直接将9弹出栈,因为9不是我们当前的答案,且数字9不会成为之后数字的答案(因为7比9小,且在9的右边),于是,我们可以删去很多数字。将9弹出后,栈顶元素3符合要求,于是7的答案是3。 随后将7压入单调栈。
同理,当遇见数字4时,栈顶元素7比4大,因此可以直接弹出7,然后栈顶元素3小于4,为答案,最后将4弹入栈。
计算数字5时,栈顶元素4符合要求,因此答案为4,随后将5 弹入栈。
因此,在维护单调栈时,我们只要把栈中比当前大的元素删除,最后的栈顶即为答案。
acwing例题1:单调栈
#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int s[N],tt=0;
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
while(a[i]<=s[tt]&&tt>0)tt--;
if(tt==0)cout<<"-1 ";
else cout<<s[tt]<<" ";
s[++tt] = a[i];
}
return 0;
}
acwing例题2:城市游戏
思路:我们对每一行进行枚举,在每一行中,我们记录l[i] 以当前行为底部,第i列开始往左边所能形成的最大面积,r[i] 为以当前行为底部,第i列往右边所能形成的最大面积。
以l[i] 为例,i列 的F高度为b[i],即寻找当前行左边第一个小于b[i] 的值。
r[i]同理
#include <bits/stdc++.h>
#define pb push_back
#define debug(x) cerr<<#x<<'='<<x<<'\\n'
#define debugg(x,y) cerr<<#x<<'='<<x<<','<<#y<<'='<<y<<'\\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 1010 ,M = 1000010,mod = 998244353;
ll qmi(ll,ll);ll read();ll gcd(ll,ll);
int n,m;
char s[N][N];
int f[N][N];
int b[N];
int stk[N],tt,l[N],r[N];
signed main()
{
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf(" %c",&s[i][j]);
int res = 0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
if(s[i][j]=='F')
b[j]++;
else b[j] = 0;
tt = 0;
stk[0] = 0;
for(int j=1;j<=m;j++)
{
while(tt&&b[stk[tt]]>=b[j])
--tt;
l[j] = (j-stk[tt])*b[j];
stk[++tt] = j;
}
tt = 0;
stk[0] = m+1;
for(int j = m;j>=1;j--)
{
while(tt&&b[stk[tt]]>=b[j])
--tt;
r[j] = (stk[tt]-j)*b[j];
stk[++tt] = j;
}
for(int j=1;j<=m;j++)
res = max(res,l[j]+r[j]-b[j]);
}
cout<<res*3;
return 0;
}
ll qmi(ll a,ll b) {ll res=1;a%=mod; for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
inline ll read(){ll s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
acwing例题3:构造数组
容易发现,这道题让我们构造一个序列,其峰值不多于一个
用l[i] 数组表示以第i个数为峰值,其左边的数所能形成的最大值,r[i] 数组表示以第i个数为峰值,其右边的数所能形成的最大值。
下面以计算l[i]为例:
回想单调栈的功能:找到一个数左边第一个比他小的数
若i左边第一个比它小的数字为 k 则 l[i] = l[k]+ (i-l)*m[i] ,于是我们又能用单调栈计算了。
计算出l和r数组后,由于两端是独立的,所以直接取最大值即可。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 500010;
typedef long long ll;
ll m[N],l[N],r[N],res[N];
ll stk[N],tt = 0;;
int main()
{
int n;cin>>n;
for(int i=1;i<=n;i++)
scanf("%lld",&m[i]);
for(int i=1;i<=n;i++)
{
while(tt&&m[stk[tt]]>=m[i])
--tt;
l[i] = l[stk[tt]]+(i-stk[tt])*m[i];
stk[++tt] = i;
}
tt = 0;
stk[++tt] = n+1;
for(int i=n;i>=1;i--)
{
while(tt&&m[stk[tt]]>=m[i])
--tt;
r[i] = r[stk[tt]]+(stk[tt]-i)*m[i];
stk[++tt] = i;
}
ll sum = 0;
int k = 0;
for(int i=1;i<=n;i++)
{
if(l[i-1]+r[i]>sum)
{
sum = l[i-1]+r[i];
k = i;
}
}
res[k] = m[k];
for(int i=k-1;i>=1;i--)
res[i] = min(m[i],res[i+1]);
for(int i=k+1;i<=n;i++)
res[i] = min(m[i],res[i-1]);
for(int i=1;i<=n;i++)
printf("%lld ",res[i]);
return 0;
}
以上是关于单调栈的应用的主要内容,如果未能解决你的问题,请参考以下文章