郵箱:[email protected]
題意:
給出一個矩陣,求全1子矩陣的最大面積
解析:
開局的處理方式和最大求和子矩陣類似,壓縮處理。
預處理h[i][j],表示第i行第j列往上(行數遞減方向)可以接上的全1串的最長長度,然後處理第一行到第i行的ans時,就可以看成處理h[i]一行了
eg:
n=3 m=4
M陣列 H陣列
0 1 1 0 0 1 1 0
1 1 1 1 --> 1 2 2 1
1 0 1 0 2 0 3 0
接下來,對於每一行該怎麼處理?
最大面積一定是某一個點的高 * 往左右延大於等於這個高的長度,所以只要對於每個高,處理出其可以延伸的左端點和右端點,ans=max(ans,H*len)
單調棧:
假設H陣列如下
維護一個單調不減的棧,首先s[0]=0,h[1]=1>0,入棧s[1]=1(下標),h[2]>s[1],入棧s[2]=2,同理s[3]=3
這個時候,每個入棧的都比前面的大,即當前為最大,故當前高度的左端點就是前面那個的下標,有 L[1]=0,L[2]=1,L[3]=2
(若i可以往左延伸到i-j,L[i]==i-j-1,同理R[i]==i+j+1)
如果將要入棧的數比棧頂元素小,那麼說明兩點:
- 對於棧頂的那個下標,已經延伸不到當前點了,就可以確定下其右端點R了
- 對於入棧元素,棧頂下標的高度可以讓其延伸,那麼就使其出棧,直到延伸不到棧頂元素為止,就可以得到將要入棧下標的左端點L
eg:
h[4]=2,比s[3]小,所以s[3]出棧,出棧的時候,s[3]為3,即第3個數不能延伸到4了,所以R[3]=4,這個時候,下標為3高度為3的左右端點已經得出,ans=max(ans,3*(4-2-1))
至於為什麼相同的不出棧
其實出不出都可以,就是寫法上的區別
首先,同一段上的相同高度,只要有一個正確,就可以了
eg:h陣列 3 2 4 2 3 那麼len[2]=1~5,len[4]=1~5,其中就算有一個是變小了,也不會影響ans=2*5=10
假設不出棧,h[s[2]]==h[4]==2,本來這個點是可以延伸到的,但是我們不出棧,所以第二個數的L不準確了,但是第一個數的R準確了,那麼第一個數因為遇到不相同的出棧了,得到的就是準確的
如果出棧的話,前面那個點的R就不準確了,但是接下來的相同的數的L是準確的,只要遇到一個不相同的使任意一個出棧,那麼最後一個相同的數的R就是準確的了,因此得到的還是一段準確的區間
程式碼:
#define N 2009
int n,m;
int M[N][N];
int h[N][N];
int ans;
int s[N],L[N],R[N];
void fin(int row){//相同不出棧
s[0]=0;int top=0;
h[row][m+1]=0;//用於得到最後沒出棧元素的R
for(int i=1;i<=m+1;i++){
int ar=s[top];
while(h[row][i]<h[row][ar]){
R[ar]=i;top--;
ar=s[top];
}
L[i]=ar;s[++top]=i;
}
for(int i=1;i<=m;i++)
if(h[row][i])
ans=max(ans,h[row][i]*(R[i]-L[i]-1));
}
void fin_(int row){//相同出棧
s[0]=0;int top=0;
h[row][m+1]=0;
for(int i=1;i<=m+1;i++){
int ar=s[top];
while(h[row][i]<=h[row][ar]){
R[ar]=i;top--;
if(top<0)break;//最後m+1的時候把s[0]也出棧了
ar=s[top];
}
L[i]=ar;s[++top]=i;
}
for(int i=1;i<=m;i++)
if(h[row][i])
ans=max(ans,h[row][i]*(R[i]-L[i]-1));
}
int main(){
while(scanf("%d%d",&n,&m)!=EOF){
mmm(h,0);ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&M[i][j]);
if(M[i][j])h[i][j]=h[i-1][j]+1;
}
}
for(int i=1;i<=n;i++)fin(i);
printf("%d\n",ans);
}
}
/*
4 4
0 1 1 1
1 0 1 1
1 1 1 1
1 1 1 0
*/