单调栈
单调栈是一种可以在线性时间维护比当前数小的最大值
模板题:
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式
第一行包含整数 N,表示数列长度。
第二行包含 N 个整数,表示整数数列。
输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。
数据范围
1≤N≤105
1≤数列中元素≤109
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
思路:通过找规律可以发现,在一个序列中比后面的数大的数永远也不会成为比之后的数小的最大数,所以每次插入新数的时候都将不符合条件的数直接扔掉即可。这种只在结尾处修改的数据结构,又符合单调性则为单调栈
#include<iostream>
#include<cstdio>
#include<string.h>
#define MAXN 100050
using namespace std;
int n,stk[MAXN],tt=0;
int x;
void op(int x){
if(tt==0){
cout << -1 << " ";
stk[++tt]=x;
return ;
}
else if(stk[tt]>=x){
tt--;
op(x);
}
else{
cout << stk[tt] << " ";
stk[++tt]=x;
return ;
}
}
int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> x;
while(tt&&stk[tt]>=x) tt--;
if(tt) cout << stk[tt] << " ";
else cout << -1 << " ";
//op(x);
stk[++tt]=x;
}
return 0;
}
直方图钟的矩形面积
https://www.acwing.com/problem/content/133/
直方图是由在公共基线处对齐的一系列矩形组成的多边形。
矩形具有相等的宽度,但可以具有不同的高度。
通常,直方图用于表示离散分布,例如,文本中字符的频率。
现在,请你计算在公共基线处对齐的直方图中最大矩形的面积。
图例右图显示了所描绘直方图的最大对齐矩形。
输入格式
输入包含几个测试用例。
每个测试用例占据一行,用以描述一个直方图,并以整数 n 开始,表示组成直方图的矩形数目。
然后跟随 n 个整数 h1,…,hn。
这些数字以从左到右的顺序表示直方图的各个矩形的高度。
每个矩形的宽度为 1。
同行数字用空格隔开。
当输入用例为 n=0 时,结束输入,且该用例不用考虑。
输出格式
对于每一个测试用例,输出一个整数,代表指定直方图中最大矩形的区域面积。
每个数据占一行。
请注意,此矩形必须在公共基线处对齐。
数据范围
1≤n≤100000,
0≤hi≤1000000000
输入样例:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出样例:
8
4000
这里考虑用单调栈维护每一个矩形左边比他高度小的第一个位置和右边同理的位置,然后线性扫描一遍得出最大面积
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 100010
int lf[N],rt[N],q[N],st[N],hh=0,f[N];
int n;
signed main(){
while(scanf("%lld",&n),n){
for(int i=1;i<=n;i++)
scanf("%lld",&q[i]);
q[0]=q[n+1]=-1;
hh=-1;
st[++hh]=0;
for(int i=1;i<=n;i++){
while(q[st[hh]]>=q[i]) hh--;
lf[i]=i-st[hh];
st[++hh]=i;
}
hh=-1;
st[++hh]=n+1;
for(int i=n;i;i--){
while(q[st[hh]]>=q[i]) hh--;
rt[i]=st[hh]-i;
st[++hh]=i;
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,q[i]*(rt[i]+lf[i]-1));
printf("%lld\n",ans);
}
return 0;
}
城市游戏
https://www.acwing.com/problem/content/154/
有一天,小猫 rainbow 和 freda 来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。
这片土地被分成 N×M 个格子,每个格子里写着 R 或者 F,R 代表这块土地被赐予了 rainbow,F 代表这块土地被赐予了 freda。
现在 freda 要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着 F 并且面积最大。
但是 rainbow 和 freda 的 OI 水平都弱爆了,找不出这块土地,而蓝兔也想看 freda 卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为 S,它们将给你 3×S 两银子。
输入格式
第一行包括两个整数 N,M,表示矩形土地有 N 行 M 列。
接下来 N 行,每行 M 个用空格隔开的字符 F 或 R,描述了矩形土地。
每行末尾没有多余空格。
输出格式
输出一个整数,表示你能得到多少银子,即(3×最大 F 矩形土地面积)的值。
数据范围
1≤N,M≤1000
输入样例:
5 6
R F F F F F
F F F F F F
R R R F F F
F F F F F F
F F F F F F
输出样例:
45
这道题在前一题的基础上,对每一行都考虑直方图的最大矩形即可,数据范围也很友好
#include<bits/stdc++.h>
using namespace std;
#define N 1010
#define int long long
int n,m;
int g[N][N],s[N][N];
int lf[N][N],rt[N][N];
int hh,q[N];
void get(int source[],int bound[]){
hh=0;
source[0]=-1;
for(int i=1;i<=m;i++){
while(source[q[hh]] >= source[i]) hh--;
bound[i]=q[hh];
q[++hh]=i;
}
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
char op[2];
scanf("%s",op);
if(*op=='F') g[i][j]=1;
else g[i][j]=0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(g[i][j]==0) s[i][j]=0;
else s[i][j]=s[i-1][j]+1;
for(int i=1;i<=n;i++){
get(s[i],lf[i]);
reverse(s[i]+1,s[i]+1+m);
get(s[i],rt[i]);
}
int ans=0;
for(int i=1;i<=n;i++)
for(int j=1,k=m;j<=m;j++,k--)
ans=max(ans,s[i][j]*(m-lf[i][k]-rt[i][j]));
printf("%lld\n",ans*3);
return 0;
}
最大面积 今日头条2021笔试题
给定一个 N×M 的 01 矩阵,矩阵下标从 0 开始。
有 Q 个询问,第 i 个询问为:将矩阵中 (xi,yi) 的元素改成 0 之后,只包含 1 的子矩阵的最大面积是多少。
注意:
每次询问均是独立的。
询问方格内元素可能本来就是 0。
子矩阵的面积是指矩阵的大小。
输入格式
第一行包含两个整数 N,M。
接下来 N 行,每行包含 M 个 01 字符。
再一行包含整数 Q。
接下来 Q 行,每行包含 2 个整数 (xi,yi)。
输出格式
每个询问输出一行一个结果,表示最大面积。
数据范围
对于 20% 的数据,1≤N,M,Q≤10
对于 50% 的数据,1≤N,M,Q≤100
对于 100% 的数据,1≤N,M≤2000,1≤Q≤105, 0≤xi<n,0≤yi<m
输入样例:
4 2
10
11
11
11
3
0 0
2 0
3 1
输出样例:
6
3
4
在前一题的基础上,这道题要考虑的是挖去点的向上的,向下的,向左的和向右的的条形图的最大矩形。因此只需要做四次预处理之后,再对上面,下面,左侧,右侧四个方向的最大矩形值取max即可
#include<bits/stdc++.h>
using namespace std;
#define N 2010
#define int long long
int n,m,Q;
char g[N][N];
int R[N],L[N],D[N],U[N],lf[N][N],rt[N][N],s[N][N],q[N],l[N],r[N];
int cal(int h[],int n){
int hh=0;
h[0]=h[n+1]=-1;
q[0]=0;
for(int i=1;i<=n;i++){
while(h[q[hh]]>=h[i]) hh--;
l[i]=q[hh];
q[++hh]=i;
}
q[0]=n+1;
hh=0;
for(int i=n;i;i--){
while(h[q[hh]]>=h[i]) hh--;
r[i]=q[hh];
q[++hh]=i;
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,h[i]*(r[i]-l[i]-1));
return ans;
}
void init(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
if(g[i][j]=='0') s[i][j]=0;
else s[i][j]=s[i-1][j]+1;
U[i]=max(U[i-1],cal(s[i],m));
}
memset(s,0,sizeof s);
for(int i=n;i;i--){
for(int j=1;j<=m;j++)
if(g[i][j]=='0') s[i][j]=0;
else s[i][j]=s[i+1][j]+1;
D[i]=max(D[i+1],cal(s[i],m));
}
memset(s,0,sizeof s);
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++)
if(g[j][i]=='0') s[i][j]=0;
else s[i][j]=s[i-1][j]+1;
L[i]=max(L[i-1],cal(s[i],n));
}
memset(s,0,sizeof s);
for(int i=m;i>=1;i--){
for(int j=1;j<=n;j++)
if(g[j][i]=='0') s[i][j]=0;
else s[i][j]=s[i+1][j]+1;
R[i]=max(R[i+1],cal(s[i],n));
}
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",g[i]+1);
init();
scanf("%lld",&Q);
while(Q--){
int x,y;
scanf("%lld%lld",&x,&y);
x++,y++;
cout << max(max(U[x-1],D[x+1]),max(L[y-1],R[y+1])) << endl;
}
return 0;
}
板子练习:https://www.luogu.com.cn/problem/P5788