版权声明:欢迎转载评论 https://blog.csdn.net/m0_37809890/article/details/82851926
单调栈
通常来说,单调栈有3个操作:入栈,出栈,求栈中的最值。可以维护最小值,最大值,或者其它比较函数。三个操作都在O(1)内进行。
实现
维护一个始终保持其中元素单调的栈,如果即将入栈的新元素会破坏单调性,就弹出旧元素,直到新元素不会破坏单调性。
习题
XJTUOJ 1071 单调栈
找不到xjtuoj的朋友可以用下一题来代替。
求权值最大的区间,区间的权值定义为区间长度乘以区间最小值,也即一个直方图中的最大矩形面积。(这道题浪费了我一年的生命)
正反各跑一遍单调栈,记录以每个位置的元素为最小值的最长区间左右端点。
(这也是单调栈最经典的应用)
int save[M],lef[M],rig[M];
int main(void)
{
int n = read();
for(int i=1;i<=n;i++)
save[i] = read();
stack<int> st; //存放下标
for(int i=1;i<=n;i++)
{
while(!st.empty() && save[st.top()]>save[i])
{
rig[st.top()] = i-1;
st.pop();
}
st.push(i);
}
while(!st.empty())
{
rig[st.top()] = n;
st.pop();
}
for(int i=n;i;i--)
{
while(!st.empty() && save[st.top()]>save[i])
{
lef[st.top()] = i+1;
st.pop();
}
st.push(i);
}
while(!st.empty())
{
lef[st.top()] = 1;
st.pop();
}
ll ans = 0;
for(int i=1;i<=n;i++)
{
ans = max(ans,1ll*save[i]*(rig[i]-lef[i]+1));
}
printf("%lld\n",ans );
return 0;
}
POJ 2796 Feel Good
求权值最大的区间,区间的权值定义为区间长度乘以区间和。
/* LittleFall : Hello! */
#include <stack>
#include <iostream>
#include <cstdio>
using namespace std; typedef long long ll;
inline int read(); inline void write(int x);
const int M = 100016, MOD = 1000000007;
int save[M];
int lef[M],rig[M];
ll sum[M];
inline ll cal(int i)
{
return (sum[rig[i]] - sum[lef[i]-1])*save[i];
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n = read();
for(int i=1;i<=n;i++)
{
save[i] = read();
sum[i] = sum[i-1] + save[i];
}
stack<int> st;
for(int i=1;i<=n;i++)
{
while(!st.empty() && save[st.top()]>save[i])
{
rig[st.top()] = i-1;
st.pop();
}
st.push(i);
}
while(!st.empty())
{
rig[st.top()] = n;
st.pop();
}
for(int i=n;i;i--)
{
while(!st.empty() && save[st.top()]>save[i])
{
lef[st.top()] = i+1;
st.pop();
}
st.push(i);
}
while(!st.empty())
{
lef[st.top()] = 1;
st.pop();
}
int ans = 0;
for(int i=1;i<=n;i++)
if(ans==0 || cal(i) > cal(ans))
ans = i;
cout << cal(ans) << endl;
cout << lef[ans] << " " << rig[ans] << endl;
return 0;
}
inline 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;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
同上题,维护一个前缀和即可。
POJ 3494 Largest Submatrix of All 1’s
求2000*2000的01矩阵中最大的全为1的矩形的面积。
首先O(nm)处理出每个1到它下方最近的1的距离,然后以每行作为子矩形的上边界,问题就转化为求直方图的最大矩形面积。
char save[M][M];
int down[M][M];
int lef[M],rig[M];
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(save,0,sizeof(save));
memset(down,0,sizeof(down));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
save[i][j] = read();
for(int j=1;j<=m;j++) //计算每个1下方有多少个1
for(int i=1, lst=0; i<=n+1; i++) //lst表示上一个0出现的位置
if(save[i][j]==0)
{
for(int k=lst+1;k<=i-1;k++)
down[k][j] = i-k;
lst = i;
}
int ans = 0;
for(int i=1;i<=n;i++)
{
stack<int> st; //单调栈
for(int j=1;j<=m;j++)
{
while(!st.empty() && down[i][st.top()]>down[i][j])
{
rig[st.top()] = j-1;
st.pop();
}
st.push(j);
}
while(!st.empty())
{
rig[st.top()] = m;
st.pop();
}
for(int j=m;j;j--)
{
while(!st.empty() && down[i][st.top()]>down[i][j])
{
lef[st.top()] = j+1;
st.pop();
}
st.push(j);
}
while(!st.empty())
{
lef[st.top()] = 1;
st.pop();
}
for(int j=1;j<=m;j++)
{
//printf("%d %d %d\n",down[i][j],lef[j],rig[j] );
ans = max(ans,down[i][j]*(rig[j]-lef[j]+1));
}
}
printf("%d\n",ans );
}
return 0;
}
HDU5033 Building (单调栈好题)
有1e5个高度在1e7以内的大楼插在坐标轴上方,1e5个询问,每次问一个地面上的点能看到多少角度的天空。
把询问离线后看成高度为0的楼,跑两次单调栈得出每个楼的顶端向左向右能看到哪些楼。
这道题满足单调栈的性质,定义楼A遮挡楼B为B只能看到A而看不到A之前的所有楼。假设现在要插入楼B,此时栈顶的楼是楼A。
如果楼A可以遮挡楼B,就把B压入栈中。
如果楼A不能遮挡B,那么B之后的楼要么被B遮挡,要么被A之前的楼遮挡,楼A就卵用没有了,弹出楼A,继续判断。
写完这段话,我才明白了单调栈的真实含义。
struct Block
{
int x;
int h;
int q; //查询编号
}block[M];
struct View
{
int x;
int l;
int r;
}view[M];
vector<int> st;
int update(int x,int h)
{
while(!st.empty() && block[st.back()].h<=h)
st.pop_back();
while(st.size()>1)
{
int x1 = block[st[st.size()-2]].x;
int h1 = block[st[st.size()-2]].h;
int x2 = block[st.back()].x;
int h2 = block[st.back()].h;
if(1ll*abs(x-x2)*(h1-h)>=1ll*abs(x-x1)*(h2-h))
st.pop_back();
else
break;
}
return st.empty() ? 0 : st.back();
}
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int T = read();
st.reserve(M); //栈
for(int xT=1;xT<=T;xT++)
{
memset(block,0,sizeof(block));
memset(view,0,sizeof(view));
int n = read();
for(int i=1;i<=n;i++)
{
block[i].x = read();
block[i].h = read();
}
int q = read();
for(int i=1;i<=q;i++)
{
view[i].x = read();
block[n+i].x = view[i].x;
block[n+i].q = i;
}
sort(block+1,block+n+q+1,[](const Block&a,const Block&b){
return a.x<b.x;
});
st.clear();
for(int i=1;i<=n+q;i++)
{
view[block[i].q].l = update(block[i].x, block[i].h);
st.push_back(i);
}
st.clear();
for(int i=n+q;i;i--)
{
view[block[i].q].r = update(block[i].x, block[i].h);
st.push_back(i);
}
printf("Case #%d:\n",xT );
for(int i=1;i<=q;i++)
{
int xl = block[view[i].l].x;
int hl = block[view[i].l].h;
int xr = block[view[i].r].x;
int hr = block[view[i].r].h;
int x = view[i].x;
double ans = atan(1.0*hl/(x-xl)) + atan(1.0*hr/(xr-x));
printf("%.10f\n",180-ans/3.14159265358979*180 );
}
}
return 0;