poj 2559(单调栈)
对于此题,我们需要找到每个点分别向左右能够扩展到的连续长度,能够扩展的条件为:这些区间的元素值都都大于等于当前点的值。
使用单调栈维护从起点到当前点的递增序列,如果栈顶元素大于当前点,我们就一直将栈中元素弹出,直到遇到小于当前点的元素,以此来维护栈的递增性,显然此时的点最远可以扩展到当前栈元素的位置-1,即我们找到了当前点的扩展边界。我们需要证明的是:前面弹出的元素对之后的点没有影响,即i之前大于h[i]的元素必然不是h[i+1]可以扩展到的边界。我们可以分情况讨论
- 若h[i+1]>h[i] 那么 h[i+1]显然无法再向左扩展,边界是h[i],证明是对的
- 若h[i+1]<=h[i] 那么 之前({j<i , h[j]>h[i]})显然都是满足扩展条件的,那么这些点都不能成为边界,即因为h[i]而弹出的元素对h[i+1]是没有影响的。证明是对的
这样的证明是具有递推性的,我们可以用数学归纳法证明其正确性。
由此我们可以线性求得每个元素向左扩展的边界,向右扩展的边界只需倒着做一遍相同的操作即可,总复杂度是O(n)。
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <vector>
#define ll long long
#define inf 1000000000LL
#define mod 1000000007
using namespace std;
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int N=1e5+10;
int L[N],R[N],st[N],h[N];
int main(){
while(true){
int n=read();
if(n==0) break;
for(int i=0;i<n;i++) h[i]=read();
int t=0;
for(int i=0;i<n;i++){
while(t>0&&h[st[t-1]]>=h[i]) t--;
L[i]=t==0?-1:st[t-1];
st[t++]=i;
}
t=0;
for(int i=n-1;i>=0;i--){
while(t>0&&h[st[t-1]]>=h[i]) t--;
R[i]=t==0?n:st[t-1];
st[t++]=i;
}
ll ans=0;
for(int i=0;i<n;i++){
ans=max(ans,(ll)h[i]*(R[i]-L[i]-1));
}
printf("%lld
",ans);
}
return 0;
}