2019牛客暑期多校訓練營(第二場)----H-Second Large Rectangle

首先發出題目鏈接:
鏈接:https://ac.nowcoder.com/acm/contest/882/H
來源:牛客網
涉及:單調棧

點擊這裏回到2019牛客暑期多校訓練營解題—目錄貼


題目如下:
在這裏插入圖片描述
在這裏插入圖片描述
第一次接觸矩陣大小的題,可能是自己做題做的比較少 ,後來看解析知道用單調棧可以解決這類問題,收穫還是挺大的。


首先可以用一個二維數組hh存一下矩陣每一個元素可以往上延伸(前提是上方爲1)的長度,比如說當原矩陣爲
[1101011111111110]\begin{bmatrix}1&1&0&1\\0&1&1&1\\1&1&1&1\\1&1&1&0\end{bmatrix}
那麼延伸矩陣hh
[1101021213232430]\begin{bmatrix}1&1&0&1\\0&2&1&2\\1&3&2&3\\2&4&3&0\end{bmatrix}
很明顯,如果某一個元素是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行
[1323]\begin{bmatrix}1&amp;3&amp;2&amp;3\end{bmatrix}

單看每一行,我們發現特別像單調棧的金典例題(沒錯,就是單調棧),每一個數字代表着小矩陣的高度。但是我們不是算最大矩陣,而是算第二大矩陣,所以還要算出所有矩陣的大小,而不是僅僅想金典例題一樣只算最大矩陣的大小。

於是得到一種方法:
對每一行進行兩次單調遞增單調棧,得出每一行的的每一個點(代表矩陣的高)可以向左或者向右延伸多少:
1.第一次單調棧從左往右入棧,出棧即可得到向左延伸的距離。
2.第二次單調棧從右往左入棧,出棧即可得到向右延伸的距離。

向左向右延伸的長度與向上延伸的距離得到了,就得到此矩形的面積(記得創建兩個數組llrr來存最大延伸距離,只用一維的就行,我們每考慮一行就總結一行)。

	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數組爲
[1101023213636460]\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}


每一個最大延伸合法矩陣的面積全部算出來,那麼第二大合法矩陣有三種情況:
1.目前所有最大延伸合法矩陣的第二大的矩陣爲答案
2.目前所有最大延伸合法矩陣的第一大的矩陣減少一行矩陣爲答案
3.目前所有最大延伸合法矩陣的第一大的矩陣減少一列矩陣爲答案

但是注意:如果第一大矩陣有兩個,那麼答案就是第一大矩陣的面積。

還有:在遍歷每一個最大延伸合法矩陣時,可能重複遍歷了同一個矩陣,所以如果當前遍歷的矩陣更大,要比較當前最大矩陣與目前遍歷的矩陣是不是同一個矩陣(判斷矩陣向左延伸的列數和向右延伸的列數是不是相同),這就是下方結構體中checkcheck函數的作用

結構體pushpush函數,則把這個值的的大小以及行列數傳入,與當前最大值進行判斷,判斷方法與更新狀態見下方代碼

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)))); 

舉個例子:

原矩陣爲
[111010111]\begin{bmatrix}1&amp;1&amp;1\\0&amp;1&amp;0\\1&amp;1&amp;1\end{bmatrix}
那麼延伸矩陣hh
[111020131]\begin{bmatrix}1&amp;1&amp;1\\0&amp;2&amp;0\\1&amp;3&amp;1\end{bmatrix}

1.遍歷延伸矩陣第一行[111]\begin{bmatrix}1&amp;1&amp;1\end{bmatrix}
得到右邊界數組爲r={3,3,3}r=\left\{3,3,3\right\},左邊界數組爲r={1,1,1}r=\left\{1,1,1\right\}
於是sum[1]={3,3,3}sum[1]=\left\{3,3,3\right\}(這裏三個面積爲3的矩陣是同一個矩陣)

2.遍歷延伸矩陣第二行[020]\begin{bmatrix}0&amp;2&amp;0\end{bmatrix}
得到右邊界數組爲r={1,2,3}r=\left\{1,2,3\right\},左邊界數組爲r={1,2,3}r=\left\{1,2,3\right\}
於是sum[2]={0,2,0}sum[2]=\left\{0,2,0\right\}

3.遍歷延伸矩陣第三行[111]\begin{bmatrix}1&amp;1&amp;1\end{bmatrix}
得到右邊界數組爲r={1,2,3}r=\left\{1,2,3\right\},左邊界數組爲r={1,2,3}r=\left\{1,2,3\right\}
於是sum[3]={1,3,1}sum[3]=\left\{1,3,1\right\}


代碼如下:

#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))));//輸出答案
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章