首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/882/H
来源:牛客网
涉及:单调栈
点击这里回到2019牛客暑期多校训练营解题—目录贴
题目如下:
第一次接触矩阵大小的题,可能是自己做题做的比较少 ,后来看解析知道用单调栈可以解决这类问题,收获还是挺大的。
首先可以用一个二维数组
h
h
h存一下矩阵每一个元素可以往上延伸(前提是上方为1)的长度,比如说当原矩阵为
[
1
1
0
1
0
1
1
1
1
1
1
1
1
1
1
0
]
\begin{bmatrix}1&1&0&1\\0&1&1&1\\1&1&1&1\\1&1&1&0\end{bmatrix}
⎣⎢⎢⎡1011111101111110⎦⎥⎥⎤
那么延伸矩阵
h
h
h为
[
1
1
0
1
0
2
1
2
1
3
2
3
2
4
3
0
]
\begin{bmatrix}1&1&0&1\\0&2&1&2\\1&3&2&3\\2&4&3&0\end{bmatrix}
⎣⎢⎢⎡1012123401231230⎦⎥⎥⎤
很明显,如果某一个元素是0,那么一定不能往上面延伸。
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int t;
scanf("%1d",&t);
if(t==1) h[i][j]=h[i-1][j]+1;
else h[i][j]=0;
}
}
于是就得到了矩阵中每一个点可以往上延伸的距离长度(合法矩阵的高 or 一条合法的直线)。
得到了每一个最大合法矩阵的高,我们就要计算最大合法矩阵可以向左或者向右延伸多少。
注意,我们不能看每一个点能向左或者向右延伸多少,而应该看合法矩阵的高(合法直线)能向左或者向右延伸多少(合法直线左右平移得到合法平面&矩阵)
我们观察上面原矩阵的第3行
[
1
3
2
3
]
\begin{bmatrix}1&3&2&3\end{bmatrix}
[1323]
单看每一行,我们发现特别像单调栈的金典例题(没错,就是单调栈),每一个数字代表着小矩阵的高度。但是我们不是算最大矩阵,而是算第二大矩阵,所以还要算出所有矩阵的大小,而不是仅仅想金典例题一样只算最大矩阵的大小。
于是得到一种方法:
对每一行进行两次单调递增单调栈,得出每一行的的每一个点(代表矩阵的高)可以向左或者向右延伸多少:
1.第一次单调栈从左往右入栈,出栈即可得到向左延伸的距离。
2.第二次单调栈从右往左入栈,出栈即可得到向右延伸的距离。
向左向右延伸的长度与向上延伸的距离得到了,就得到此矩形的面积(记得创建两个数组 l l l和 r r r来存最大延伸距离,只用一维的就行,我们每考虑一行就总结一行)。
for(int i=1;i<=n;i++){
vector<int> sta;//用vector创建一个单调栈
for(int j=1;j<=m+1;j++){//从左往右入栈
while(sta.size() && h[i][sta.back()]>h[i][j]){//出栈条件
r[sta.back()]=j-1;//得到某一行第sta.back()个元素向右延伸的距离
sta.pop_back();
}
sta.push_back(j);//把当前的元素的序号入栈
}
sta.clear();//清空栈
for(int j=m;j>=0;j--){//从右往左入栈
while(sta.size() && h[i][sta.back()]>h[i][j]){//出栈条件
l[sta.back()]=j+1;//得到某一行第sta.back()个元素向左延伸的距离
sta.pop_back();
}
sta.push_back(j);//把当前的元素的序号入栈
}
for(int j=1;j<=m;j++){
sum[i][j]=(r[j]-l[j]+1)*h[i][j];//sum数组存以原数组第i行第j元素为底边元素的最大矩阵的面积
if(ans.check(l[j],r[j],h[i][j])) ans.push(sum[i][j],i,j);//这个结构体稍后说
}
}
如上原矩阵的sum数组为
[
1
1
0
1
0
2
3
2
1
3
6
3
6
4
6
0
]
\begin{bmatrix}1&1&0&1\\0&2&3&2\\1&3&6&3\\6&4&6&0\end{bmatrix}
⎣⎢⎢⎡1016123403661230⎦⎥⎥⎤
每一个最大延伸合法矩阵的面积全部算出来,那么第二大合法矩阵有三种情况:
1.目前所有最大延伸合法矩阵的第二大的矩阵为答案
2.目前所有最大延伸合法矩阵的第一大的矩阵减少一行矩阵为答案
3.目前所有最大延伸合法矩阵的第一大的矩阵减少一列矩阵为答案
但是注意:如果第一大矩阵有两个,那么答案就是第一大矩阵的面积。
还有:在遍历每一个最大延伸合法矩阵时,可能重复遍历了同一个矩阵,所以如果当前遍历的矩阵更大,要比较当前最大矩阵与目前遍历的矩阵是不是同一个矩阵(判断矩阵向左延伸的列数和向右延伸的列数是不是相同),这就是下方结构体中 c h e c k check check函数的作用。
结构体 p u s h push push函数,则把这个值的的大小以及行列数传入,与当前最大值进行判断,判断方法与更新状态见下方代码。
if(x>=this->fir){
this->left=l[j],this->right=r[j],this->heigh=h[i][j],this->sec=this->fir,this->fir=x;
}
else if(x>=this->sec) this->sec=x;
创建一个结构体(我做的有点麻烦了)
struct Max{
int fir,sec;//fir为第一大矩阵的的面积,sec为第二大矩阵的面积
int left,right,heigh;//分别为第一大矩阵的往左延伸的列数,往右延伸的列数,高度
Max(){
this->fir=this->sec=this->left=this->right=this->heigh=0;
}
void push(int x,int i,int j){//考虑当前矩阵矩阵
if(x>=this->fir){
this->left=l[j],this->right=r[j],this->heigh=h[i][j],this->sec=this->fir,this->fir=x;
}
else if(x>=this->sec) this->sec=x;
}
bool check(int _l,int _r,int _h){//查重,防止重复
if(_h==this->heigh && _l==this->left && _r==this->right) return false;
else return true;
}
};
printf("%d",max(ans.sec,max((ans.right-ans.left)*ans.heigh,(ans.right-ans.left+1)*(ans.heigh-1))));
举个例子:
原矩阵为
[
1
1
1
0
1
0
1
1
1
]
\begin{bmatrix}1&1&1\\0&1&0\\1&1&1\end{bmatrix}
⎣⎡101111101⎦⎤
那么延伸矩阵
h
h
h为
[
1
1
1
0
2
0
1
3
1
]
\begin{bmatrix}1&1&1\\0&2&0\\1&3&1\end{bmatrix}
⎣⎡101123101⎦⎤
1.遍历延伸矩阵第一行
[
1
1
1
]
\begin{bmatrix}1&1&1\end{bmatrix}
[111]
得到右边界数组为
r
=
{
3
,
3
,
3
}
r=\left\{3,3,3\right\}
r={3,3,3},左边界数组为
r
=
{
1
,
1
,
1
}
r=\left\{1,1,1\right\}
r={1,1,1}
于是
s
u
m
[
1
]
=
{
3
,
3
,
3
}
sum[1]=\left\{3,3,3\right\}
sum[1]={3,3,3}(这里三个面积为3的矩阵是同一个矩阵)
2.遍历延伸矩阵第二行
[
0
2
0
]
\begin{bmatrix}0&2&0\end{bmatrix}
[020]
得到右边界数组为
r
=
{
1
,
2
,
3
}
r=\left\{1,2,3\right\}
r={1,2,3},左边界数组为
r
=
{
1
,
2
,
3
}
r=\left\{1,2,3\right\}
r={1,2,3}
于是
s
u
m
[
2
]
=
{
0
,
2
,
0
}
sum[2]=\left\{0,2,0\right\}
sum[2]={0,2,0}
3.遍历延伸矩阵第三行
[
1
1
1
]
\begin{bmatrix}1&1&1\end{bmatrix}
[111]
得到右边界数组为
r
=
{
1
,
2
,
3
}
r=\left\{1,2,3\right\}
r={1,2,3},左边界数组为
r
=
{
1
,
2
,
3
}
r=\left\{1,2,3\right\}
r={1,2,3}
于是
s
u
m
[
3
]
=
{
1
,
3
,
1
}
sum[3]=\left\{1,3,1\right\}
sum[3]={1,3,1}
代码如下:
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
int h[1005][1005];//原二维数组每一个点可以向上延伸的高度
int n,m;//题目所给
int l[1005],r[1005],sum[1005][1005];//考虑某一行时,l只该元素先可以向左延伸多少,r指该元素可以向右延伸多少,sum指某一个点延伸成矩阵时此矩阵的面积
struct Max{
int fir,sec;//fir为第一大矩阵的的面积,sec为第二大矩阵的面积
int left,right,heigh;//分别为第一大矩阵的往左延伸的列数,往右延伸的列数,高度
Max(){
this->fir=this->sec=this->left=this->right=this->heigh=0;
}
void push(int x,int i,int j){//考虑当前矩阵矩阵
if(x>=this->fir){
this->left=l[j],this->right=r[j],this->heigh=h[i][j],this->sec=this->fir,this->fir=x;
}
else if(x>=this->sec) this->sec=x;
}
bool check(int _l,int _r,int _h){//查重,防止重复
if(_h==this->heigh && _l==this->left && _r==this->right) return false;
else return true;
}
};
int main(){
scanf("%d%d",&n,&m);
Max ans;
//下面这个二重循环是创建向上延伸矩阵h
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int t;
scanf("%1d",&t);
if(t==1) h[i][j]=h[i-1][j]+1;
else h[i][j]=0;
}
}
for(int i=1;i<=n;i++){
vector<int> sta;//用vector创建一个单调栈
for(int j=1;j<=m+1;j++){//从左往右入栈
while(sta.size() && h[i][sta.back()]>h[i][j]){//出栈条件
r[sta.back()]=j-1;//得到某一行第sta.back()个元素向右延伸的距离
sta.pop_back();
}
sta.push_back(j);//把当前的元素的序号入栈
}
sta.clear();//清空栈
for(int j=m;j>=0;j--){//从右往左入栈
while(sta.size() && h[i][sta.back()]>h[i][j]){//出栈条件
l[sta.back()]=j+1;//得到某一行第sta.back()个元素向左延伸的距离
sta.pop_back();
}
sta.push_back(j);//把当前的元素的序号入栈
}
//下面这个循环考虑每一个最大延伸矩阵的面积
for(int j=1;j<=m;j++){
sum[i][j]=(r[j]-l[j]+1)*h[i][j];
if(ans.check(l[j],r[j],h[i][j])) ans.push(sum[i][j],i,j);
}
}
printf("%d",max(ans.sec,max((ans.right-ans.left)*ans.heigh,(ans.right-ans.left+1)*(ans.heigh-1))));//输出答案
}