单调栈
单调栈顾名思义就是让栈中的元素是单调的,要么递增,要么递减。同样它也满足栈的性质,先进后出。
- 单调递增栈,则从栈顶到栈底的元素是严格递增的
- 单调递减栈,则从栈顶到栈底的元素是严格递减的
练习题
- 单调栈
- 练习题
- POJ3250
- POJ2796
- BZOJ1113
- HDU1506
- POJ2559
- JDFZ2997
POJ3250
对于每一个牛来说,能看到的数目为向右数身高比它小的个数,累加就是答案。
所以可以倒着维护一个单调递增的栈,记录i之前的弹栈数目,累加。
(正着也可以,那么就是维护对于每一头牛来说,它被多少头牛看到)
Code:
#include <stdio.h>
#define MAXN 80000+100
typedef unsigned long long llu;
llu n,ans=0;
long long s_size=-1;
llu s[MAXN];
int main()
{
scanf("%llu",&n);
for(llu i=1;i<=n;i++)
{
llu x;
scanf("%llu",&x);
while(s_size>=0&&x>=s[s_size])s_size--;
s[++s_size]=x;
ans+=s_size;
}
printf("%llu\n",ans);
return 0;
}
POJ2796
- POJ2796传送门
- 这道题比较麻烦,但是思想很重要,对于每一个点,求出向左/右第一个比它大的位置,然后O(N)枚举,找出最值。
- 但是需要注意两点:
- 开long long
- 预处理前缀和
Code:
#include<stdio.h>
#include<string.h>
#include<stack>
#define MAXN 100005
typedef long long ll;
using namespace std;
ll n,x,ans,l_,r_,top;
ll a[MAXN],l[MAXN],r[MAXN],s[MAXN],sum[MAXN];
ll max(ll a,ll b){return a>b?a:b;}
int main()
{
while(~scanf("%lld",&n)&&n)
{
ans=0;
l_=r_=1;
for(ll i=1;i<=n;i++)
{
scanf("%lld",a+i);
sum[i]=sum[i-1]+a[i];
}
s[top]=0;
a[0]=a[n+1]=-1;
for(ll i=1;i<=n;i++)
{
for(x=s[top];a[x]>=a[i];x=s[top])
{
top--;
}
l[i]=x+1;
s[++top]=i;
}
top=0;
s[top]=n+1;
for(ll i=n;i>=1;i--)
{
for(x=s[top];a[x]>=a[i];x=s[top])
{
top--;
}
r[i]=x-1;
s[++top]=i;
}
for(ll i=1;i<=n;i++)
{
ll sum_=sum[r[i]]-sum[l[i]-1];
if(a[i]*sum_>ans)
{
ans=a[i]*sum_;
l_=l[i],r_=r[i];
}
}
printf("%lld\n%lld %lld\n",ans,l_,r_);
}
return 0;
}
BZOJ1113
——[Poi2008]海报PLA【土豪题】
ans与x值无关,只与高度有关。维护一个单调递增的栈,如果有一个值在栈中出现过,那么这个高度就可以被之前的抵消。
然后再用“补集转化”的思想,最多N次才能完成,减去可以抵消的次数就是答案
Code:
#include<stdio.h>
#include<string.h>
#define MAXN 1000000+100
int s[MAXN];
int x,y,top,ans;
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x,&y);
while(top>=1&&y<=s[top])
{
if(s[top]==y)
{
ans++;
}
top--;
}
s[++top]=y;
}
printf("%d\n",n-ans);
return 0;
}
HDU1506
题意就是在单位长度内,每一个矩形的宽都为1,但长度可变,题意需要求最大矩形的面积。
因此我们可以用两个数组l[i],r[i]表示,第i个点向左/右 最长能扩展到第几个点,也就是第一个小于它的点。
Ans=max{a[i]*(r[i]-l[i]+1)};
在这里我们就要用的“单调栈”来进行这神奇的功能,让时间复杂度由O(n^2)变为O(n).
Code:
#include<stdio.h>
#include<string.h>
#include<stack>
#define MAXN 100005
typedef long long ll;
using namespace std;
ll n,x=0;
ll a[MAXN],l[MAXN],r[MAXN];
stack<int>s;
ll max(ll a,ll b){return a>b?a:b;}
int main()
{
while(~scanf("%lld",&n)&&n)
{
for(ll i=1;i<=n;i++)
{
scanf("%lld",a+i);
}
while(!s.empty())s.pop();
s.push(0);
a[0]=a[n+1]=-1;
for(ll i=1;i<=n;i++)
{
for(x=s.top();a[x]>=a[i];x=s.top())
{
s.pop();
}
l[i]=x+1;
s.push(i);
}
while(!s.empty())s.pop();
s.push(n+1);
for(ll i=n;i>=1;i--)
{
for(x=s.top();a[x]>=a[i];x=s.top())
{
s.pop();
}
r[i]=x-1;
s.push(i);
}
ll max_num=-1;
for(ll i=1;i<=n;i++)
{
max_num=max(max_num,(r[i]-l[i]+1)*a[i]);
}
printf("%lld\n",max_num);
}
return 0;
}
POJ2559
同上,一道题。
JDFZ2997
这个就不给传送门了,你懂的。
这道题同样需要维护l[],r[]两个数组,但是需要进行两遍,一次是求出以某一点为最小值最大能扩张的范围,一次是以其为最大值。
对于一个第i位的数字,如果它的l[i],r[i]已知,那么以它为最值的区间个数为(r[i]-i+1)*(i-l[i]+1)
这个性质知道后就可以求出sum_max, 以及 sum_min;因为题中要求的是所有区间的最大值-最小值的差 的和,等价于所有区间最大值的和-所有区间最小值的和。∴ans=sum_max-sum_min;
Code:
#include<stdio.h>
#include<string.h>
#define INF 990000000
#define MAXN 350000
typedef long long ll;
ll n,x,sum_max,sum_min,top;
ll a[MAXN],s[MAXN],l[MAXN],r[MAXN];
int main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
{
scanf("%lld",a+i);
}
s[top]=0,a[0]=a[n+1]=INF;
for(ll i=1;i<=n;i++)
{
for(x=s[top];a[x]<=a[i];x=s[top])
{
top--;
}
l[i]=x+1;
s[++top]=i;
}
top=0,s[top]=n+1;
for(ll i=n;i>=1;i--)
{
for(x=s[top];a[x]<a[i];x=s[top])
{
top--;
}
r[i]=x-1;
s[++top]=i;
}
for(ll i=1;i<=n;i++)
{
sum_max+=a[i]*(r[i]-i+1)*(i-l[i]+1);
}
//////////////////////////////////////////////////////////////
top=0,s[top]=0,a[0]=a[n+1]=-1;
for(ll i=1;i<=n;i++)
{
for(x=s[top];a[x]>=a[i];x=s[top])
{
top--;
}
l[i]=x+1;
s[++top]=i;
}
top=0,s[top]=n+1;
for(ll i=n;i>=1;i--)
{
for(x=s[top];a[x]>a[i];x=s[top])
{
top--;
}
r[i]=x-1;
s[++top]=i;
}
for(ll i=1;i<=n;i++)
{
sum_min+=a[i]*(r[i]-i+1)*(i-l[i]+1);
}
printf("%lld\n",sum_max-sum_min);
return 0;
}
总结:单调栈的题目一定要联想到它的性质:求出某个数的左边或者右边第一个比它大或者小的元素,而且总时间复杂度O(N)。
版权声明:本文为博主原创文章,未经博主允许不得转载。