直方图中最大的矩形
【题目链接】
https://www.acwing.com/problem/content/description/133/
【题目描述】![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/54e9af4274147824f507cda54aa17c8f.png)
【输入样例】
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
【输出样例】
8
4000
【思路】
可以想到要构成最大矩形的话,最朴实的办法就是,从每个高度向两边扩展,直到找到小于当前高度h[i]
的左右边界,然后进行区间x高度
即为每个高度所能扩展最大面具,然后遍历取最大值即可
【暴力解法】
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int a[N];
LL res = 0;
int main() {
int n;
while(cin >> n, n) {
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
for(int i = 0; i < n; i++) {
LL l = i, r = i;
while(a[l] >= a[i]) l--;
while(a[r] >= a[i]) r++;
res = max(res, (r - l - 1) * (LL)a[i]);
}
cout << res << '\n';
}
return 0;
}
这样明显会超时,想到优化,有什么办法可以快速找到边界范围的下标呢,可以省去循环查找的复杂步骤,可以想到如果是单调的,那么能直接锁定左边界范围,用单调栈来存储下标,使得单调栈维护的是一个下标数组,且对应高度呈上升
对应的,要想求有边界的话,我们只需反转数组重复操作一遍即可求出右边界
- 注意:最后的数组是反转后的,故我们在求答案时对
左边界
为原数组下标的反向映射
【AC代码如下】
//根据上方思想,我们想办法快速找到每个数对应的l和r边界,可以想到用单调栈
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;
int h[N], l[N], r[N]; //分别第i个位置存储高度,可组矩阵的左边界位置or右边界位置
int q[N]; //一个单调栈,存储下标,所对应元素高度上升排列
void get_range(int a[]) {
//初始化一个哨兵,省去边界处理的麻烦操作
h[0] = -1;
int tt = 0; //栈从0开始
for(int i = 1; i <= n; i++) {
while(h[q[tt]] >= h[i]) tt--;
a[i] = q[tt];
q[++ tt] = i;
}
}
int main() {
while(cin >> n, n) {
for(int i = 1; i <= n; i++) scanf("%d", &h[i]);
//求边界位置(小于h[i])
get_range(l);
//反转数组,同样可求右边界范围
reverse(h + 1, h + 1 + n);
get_range(r);
LL res = 0;
//注意此时数组已经反转,故i,j对应的位置发生改变
for(int i = 1, j = n; i <= n; i++, j--) {
//用j代表映射过去的反着的i
res = max(res, (n + 1 - l[j] - r[i] - 1) * (LL)h[i]);
}
cout << res << endl;
}
return 0;
}
要是觉得上方代码最后的下标绕的话,用下面的方法写也是一样
【AC代码】
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int h[N],q[N],l[N],r[N];
// h为输入中的n个数
// q为单调栈里
// l为每个数左侧离自己最近且比自己小的数
// r为每个数右侧离自己最近且比自己小的数
typedef long long LL;
int main(){
int n;
while(scanf("%d",&n),n){
for(int i = 1;i <= n;i ++) scanf("%d",&h[i]); // 输入
h[0] = h[n + 1] = -1; // 为了防止越界,把0和n + 1位置赋值为-1
int tt = -1; // 将栈长度赋值为-1
q[++ tt] = 0; // 往栈里压入数0
for(int i = 1;i <= n;i ++){ // 查找每个数左侧离自己最近且比自己小的数
while(h[q[tt]] >= h[i]) tt --;
l[i] = q[tt];
q[++ tt] = i;
}
//这里重新赋值栈,反着来
tt = -1;
q[++ tt] = n + 1; //因为存的是位置下标
for(int i = n;i;i --){ // 查找每个数右侧离自己最近且比自己小的数
while(h[q[tt]] >= h[i]) tt --;
r[i] = q[tt];
q[++ tt] = i;
}
LL res = 0; // 答案
for(int i = 1;i <= n;i ++) res = max(res,(LL)h[i] * (r[i] - l[i] - 1));
printf("%lld\n",res);
}
return 0;
}
矩形牛棚
【题目链接】
https://www.acwing.com/problem/content/description/1415/
[题目描述]
思路:在这里以下边界为参照,枚举每一列的情况,对每一块的可组成方块进行累加(前缀和高度),这样的话就类似于一个二维的最大矩形的求解过程
【AC代码】
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 3010;
int n, m, p;
int hh[N][N], st[N][N];
int q[N], l[N], r[N];
int main()
{
cin >> n >> m >> p;
int a, b;
while(p --)
{
cin >> a >> b;
st[a][b] = 1;
}
for(int i = 1;i <= n; i ++)
{
for(int j = 1 ; j <= m; j++)
{
if(st[i][j] == 0) hh[i][j] = hh[i - 1][j] + 1;
}
}
int res = 0;
int tt = -1;
for(int i = 1;i <= n;i++)
{
q[++ tt] = 0;
hh[i][0] = hh[i][m+1] = -1;
for(int j=1;j<=m;j++)
{
while(hh[i][j]<=hh[i][q[tt]])tt--;
l[j]=q[tt];
q[++tt]=j;
}
tt = -1, q[++tt] = m + 1;
for(int j=m;j;j--) //记得j--,而不是加加,因为这是从后面遍历找右边的。
{
while(hh[i][j]<=hh[i][q[tt]])tt--;
r[j]=q[tt];
q[++tt]=j;
}
for(int j=1;j<=m;j++)
res=max(res, hh[i][j]*(r[j]-l[j]-1)); //不断的比较面积大小,取最大的。
}
cout<<res<<endl;
return 0;
}