2019牛客暑期多校训练营(第二场)----H-Second Large Rectangle

首先发出题目链接:
链接: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&amp;3&amp;2&amp;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&amp;1&amp;0&amp;1\\0&amp;2&amp;3&amp;2\\1&amp;3&amp;6&amp;3\\6&amp;4&amp;6&amp;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&amp;1&amp;1\\0&amp;1&amp;0\\1&amp;1&amp;1\end{bmatrix} 101111101
那么延伸矩阵 h h h
[ 1 1 1 0 2 0 1 3 1 ] \begin{bmatrix}1&amp;1&amp;1\\0&amp;2&amp;0\\1&amp;3&amp;1\end{bmatrix} 101123101

1.遍历延伸矩阵第一行 [ 1 1 1 ] \begin{bmatrix}1&amp;1&amp;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&amp;2&amp;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&amp;1&amp;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))));//输出答案
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值