首先發出題目鏈接:
鏈接:https://ac.nowcoder.com/acm/contest/882/H
來源:牛客網
涉及:單調棧
點擊這裏回到2019牛客暑期多校訓練營解題—目錄貼
題目如下:
第一次接觸矩陣大小的題,可能是自己做題做的比較少 ,後來看解析知道用單調棧可以解決這類問題,收穫還是挺大的。
首先可以用一個二維數組存一下矩陣每一個元素可以往上延伸(前提是上方爲1)的長度,比如說當原矩陣爲
那麼延伸矩陣爲
很明顯,如果某一個元素是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.第一次單調棧從左往右入棧,出棧即可得到向左延伸的距離。
2.第二次單調棧從右往左入棧,出棧即可得到向右延伸的距離。
向左向右延伸的長度與向上延伸的距離得到了,就得到此矩形的面積(記得創建兩個數組和來存最大延伸距離,只用一維的就行,我們每考慮一行就總結一行)。
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.目前所有最大延伸合法矩陣的第二大的矩陣爲答案
2.目前所有最大延伸合法矩陣的第一大的矩陣減少一行矩陣爲答案
3.目前所有最大延伸合法矩陣的第一大的矩陣減少一列矩陣爲答案
但是注意:如果第一大矩陣有兩個,那麼答案就是第一大矩陣的面積。
還有:在遍歷每一個最大延伸合法矩陣時,可能重複遍歷了同一個矩陣,所以如果當前遍歷的矩陣更大,要比較當前最大矩陣與目前遍歷的矩陣是不是同一個矩陣(判斷矩陣向左延伸的列數和向右延伸的列數是不是相同),這就是下方結構體中函數的作用。
結構體函數,則把這個值的的大小以及行列數傳入,與當前最大值進行判斷,判斷方法與更新狀態見下方代碼。
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.遍歷延伸矩陣第一行
得到右邊界數組爲,左邊界數組爲
於是(這裏三個面積爲3的矩陣是同一個矩陣)
2.遍歷延伸矩陣第二行
得到右邊界數組爲,左邊界數組爲
於是
3.遍歷延伸矩陣第三行
得到右邊界數組爲,左邊界數組爲
於是
代碼如下:
#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))));//輸出答案
}