http://www.cnblogs.com/fstang/archive/2013/05/19/3087746.html
這里的代碼沒有使用單調棧的方法:
題目:http://ac.jobdu.com/problem.php?cid=1045&pid=0
- 題目描述:
-
在一個M * N的矩陣中,所有的元素只有0和1,從這個矩陣中找出一個面積最大的全1子矩陣,所謂最大是指元素1的個數最多。
- 輸入:
-
輸入可能包含多個測試樣例。
對於每個測試案例,輸入的第一行是兩個整數m、n(1<=m、n<=1000):代表將要輸入的矩陣的大小。
矩陣共有m行,每行有n個整數,分別是0或1,相鄰兩數之間嚴格用一個空格隔開。
- 輸出:
-
對應每個測試案例,輸出矩陣中面積最大的全1子矩陣的元素個數。
- 樣例輸入:
-
2 2 0 0 0 0 4 4 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0
- 樣例輸出:
-
0 4
方法是:
1、先將0/1矩陣讀入x,對每一個非零元素x[i][j],將其更新為:在本行,它前面的連續的1的個數+1(+1表示算入自身)
比如,若某一行為0 1 1 0 1 1 1,則更新為0 1 2 0 1 2 3
2、對每一個非零元素x[i][j],在第j列向上和向下掃描,直到遇到比自身小的數,若掃描了y行,則得到一個大小為x[i][j]*(y+1)的全1子矩陣(+1表示算入自身所在行)
比如,若某一列為[0 3 4 3 5 2 1]'(方便起見,這里將列表示成一個列向量),我們處理這一列的第4個元素,也就是3,它向上可以掃描2個元素,向下可以掃描1個元素,於是得到一個4×3的全1子矩陣。
3、在這些數值中取一個最大的。
思想大概如下圖所示(空白處的0沒有標出)

對照步驟2中給出的例子,藍色的箭頭表示向上向下掃描,黑色的框表示最終得到的全1子矩陣
這樣做為什么是對的?
想一想,對那個最大的全1子矩陣,用這種方法能不能找到它呢?——肯定可以。
一個最大全1子矩陣,肯定是四個邊界中的每一個都不能再擴展了,如下圖

假設圖中全1子矩陣就是最大子矩陣,則左邊界左側那一列肯定有一個或多個0(否則就可以向左邊擴展一列,得到一個更大的全1矩陣)
對其他3個邊界有類似的情況。
然后看圖中用黑圈標出的1(其特點是:和左邊界左側的某個0在同一行),從這個1出發,按照之前的方法,向上向下掃描,就可以得到這個子矩陣。所以,肯定可以找到。
下面是我的代碼,實際實現的時候,為了提高效率,估計了一下upperbound,這個upperbound就是:在當前列,
包含x[i][col]的連續的非零序列的和,比如對某列[0 3 4 3 5 2 1]',后面6個的upperbound都是
3 + 4 + 3 + 5 + 2 + 1 = 18,對於0元素,不需要upperbound
#include <stdio.h>
int m, n;
int x[1002][1002];
int upperbound[1002][1002];
//pre處理后,x[i][j]表示原矩陣第i行中x[i][j]前面有多少個連續的1
void pre() {
for (int i = 0; i < m; i++) {
for (int j = 1; j < n; j++) {//注意,j從1開始
//每行第一個元素不用判斷,0/1都不用改變,對應每一段的第一個1也是如此
if (x[i][j] == 1 && x[i][j - 1] != 0) {
x[i][j] = x[i][j - 1] + 1;
}
}
}
}
//proc_col對第col列計算每個x[i][col]的upperbound,這個upperbound就是:在當前列,
//包含x[i][col]的連續的非零序列的和,比如對[0 3 4 3 5 2 1],后面6個的upperbound都是
//3 + 4 + 3 + 5 + 2 + 1 = 18,對於0元素,不需要upperbound
void proc_col(int col) {
for (int i = 0; i < m; i++) {
if (x[i][col] == 0) {
continue;
}
int sum = 0, j = i;
while (j < m && x[j][col] > 0) {
sum += x[j][col];
j++;
}
for (int k = i; k < j; k++) {
upperbound[k][col] = sum;
}
i = j;//之后i還會++,但是不會跳過重要的值,因為此時x[j][]=0或在界外
}
}
//逐列計算upperbound
void calc_upper(){
for (int col = 0; col < n; col++) {
proc_col(col);
}
}
//從x[row][col]向上向下掃描
int search_up_down(int row, int col) {
int cnt = 1, val = x[row][col];
for (int i = row - 1; i >= 0; i--) {
if (x[i][col] >= val) {
cnt++;
} else {
break;
}
}
for (int i = row + 1; i < m; i++) {
if (x[i][col] >= val) {
cnt++;
} else {
break;
}
}
return cnt * val;
}
//得到最大全1子矩陣的大小
int getMax() {
int max = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (x[i][j] != 0 && max < upperbound[i][j]) {
int val = search_up_down(i, j);
if (val > max) {
max = val;
}
}
}
}
return max;
}
int main(int argc, const char *argv[])
{
while(scanf("%d%d", &m, &n) != EOF) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
scanf("%d", &x[i][j]);
}
}
pre();
calc_upper();
printf("%d\n", getMax());
}
return 0;
}
單調棧的介紹:
http://www.cnblogs.com/ziyi--caolu/archive/2013/06/23/3151556.html
這里並不完備,有時間應當自己實現一下。
目前想到的最容易理解的方法是遍歷兩遍,從左往右,從右往左。每次都記錄彈出的元素,和彈出元素本身計算出來的值。
兩次逆序相加即可。(具體可以根據例子驗證)

