01矩陣找最大0子矩陣。
題目地址:玉蟾宫 - 洛谷
目录
經典題目,可以用懸線法(catenary method)或者單調棧,對於所有懸線法可以解的題目,單調棧都可以解,所以不會開一個懸線法系列。
Catenary Method
我們對於每一橫都用兩個指針掃一遍,得到每一橫裡的每一個位置的最大可拓展長度,并把他紀錄在dp的矩陣裡。
然後對於每一列,我們都掃一遍,得到每一個位置最大可向上拓展多少格,并把他紀錄在dp矩陣中。
最後我們進行統計,統計的方法是,我們便利每一行每一列,我們找出每一行最小可向上拓展的長度和最小的寬度相乘就得出面積,如此類推求出最大面積。 為什麼要找最小可向上拓展呢?
因為如果找最長會遇到出錯,因為這一個位置可以拓展到的位置不代表其他也能,找最小是為了保證正確性。
這裡要注意我們對於寬度的保存,是有一個最左可拓展和最右可拓展矩陣,裡面放的是座標不是距離,所以找最小寬度,就是找最大的向左座標,最小的向右座標。
代碼流程
初始化
讀入數據,最左和最右一開始都是自己,最上就是1
#include <iostream>
#include <cstring>
using namespace std;
int n, m, ans =0;
int a[2010][2010], lft[2010][2010], rght[2010][2010], up[2010][2010];
int main(){
memset(a, 0, sizeof(a));
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
char ch;
cin >> ch;
if(ch == 'F')a[i][j] = 1;
lft[i][j] = j;
rght[i][j] = j;
up[i][j] = 1;
}
}
}
初始化最左
利用遞推思想,lft[i][j] 為當前坐標可達到的最左座標。若當前位置非0,就是可走,否則不可以走,如果旁邊是邊界,不可以走,則有if(a[i][j] && a[i][j - 1]) lft[i][j] = lft[i][j - 1];
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(a[i][j] && a[i][j - 1]){
lft[i][j] = lft[i][j - 1];
}
}
}
初始化最右
利用遞推思想,rght[i][j]為當前坐標可到達最右的座標。思想跟初始化最左相似,不解釋了,可以注意的是我們的內置循環為倒循環,因為從右走到左過來。
for(int i = 1; i <= n; i++){
for(int j = m; j >= 1; j++){
if(a[i][j] && a[i][j + 1])rght[i][j] = rght[i][j + 1];
}
}
Core Part
我們從(1,1)出發,每走完一層更新up的數據,因為第一層最高的up就是1,我們接下來只需要分析上一層的同一列是否為障礙物就可以了,並且同時間我們也找出最大寬度,結算一次。
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(a[i][j] && a[i - 1][j]){
lft[i][j] = max(lft[i][j], lft[i - 1][j]);
rght[i][j] = min(rght[i][j], rght[i - 1][j]);
up[i][j] = up[i - 1][j] + 1;
}
if(!a[i][j])continue;
int x = rght[i][j] - lft[i][j];
ans = max(ans, up[i][j] * x);
}
}
完整代碼
#include <iostream>
using namespace std;
int n, m;
int a[2010][2010], lft[2010][2010], rght[2010][2010], ans = 0, up[2010][2010];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
char ch;
cin >> ch;
if(ch == 'F')a[i][j] = 1;
lft[i][j] = j;
rght[i][j] = j;
up[i][j] = 1;
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(a[i][j] && a[i][j - 1])lft[i][j] = lft[i][j - 1];
}
}
for(int i = 1; i <= n; i++){
for(int j = m; j >= 1; j--){
if(a[i][j] && a[i][j + 1])rght[i][j] = rght[i][j + 1];
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(a[i][j] && a[i - 1][j]){
lft[i][j] = max(lft[i - 1][j], lft[i][j]);
rght[i][j] = min(rght[i - 1][j], rght[i][j]);
up[i][j] = up[i - 1][j] + 1;
}
if(!a[i][j])continue;
int x = rght[i][j] - lft[i][j] + 1;
ans = max(ans, x * up[i][j]);
}
}
cout << ans * 3;
return 0;
}
Monotonic Stack
積水面積複雜版
只要得到每一個格子的可向上拓展高度就可以得到把每一行當成積水面積差不多的題目來做了。
只不過我們要讓棧內的元素保持升序,假設我們棧裡的元素是升序並且有k個元素,如果第k+1元素進來,他小於棧頂元素,也就是說前k個元素中那些比他大的元素都是不可能成為這個矩形的高度的,但同時這些過高的矩形本身有可能就是最大面積,所以我們把他們彈出來進行結算,如此類推。我們對每一行都做一樣的操作,取得最優值就可以了。
但是有機會遇到一樣高的元素,那麼我們就開結構體,把這一個元素的寬加1,就可以了。
就像下面的一樣如果左邊是題目給的資料,有邊是我們放進棧的情況。
最後我們做總結就可以了。
如何做總結呢?
先把棧頂的矩形拿出來,算一下他的面積,再把他彈出去,把他的寬度繼承給下一個棧頂元素,如此類推,我們可以拿到所有棧內元素,以他們為高度的所有可能最大面積了。
代碼
初始化
先讀入數據
#include <iostream>
#include <vector>
using namespace std;
int n, m;
int f[2050][2050];
char c;
struct node{
int val, len;
}a[2050];
vector<node> s;
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> c;
if(c == 'F') f[i][j] = f[i - 1][j] + 1;
}
}
return 0;
}
Core Part
由於每一個位置可向上拓展的地方都記錄下來了,所以我們可以直接枚舉每一行進行總結。
每次彈棧後,把該矩形的寬度繼承下去,並且彈完後,把所有寬度賦值給新進來的數值。
void check(int r){
memset(a, 0, sizeof(a));
a[1].val = f[x][1];
a[1].len = 1;
while(!s.empty())s.pop_back();
for(int i = 1; i <= m; i++){
int width = 0;
while(!s.empty() && f[x][i] <= s.back().val){
width += s.back().len;
maxArea = max(maxArea, width * s.back().val);
s.pop_back();
}
a[i].val = f[x][i];
a[i].len = width + 1;
s.push_back(a[i]);
}
int w = 0;
while(!s.empty()){
w+=s.back().len;
maxArea = max(maxArea, width * s.back().val);
s.pop_back();
}
}
完整代碼
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
int n, m, maxArea;
int f[2050][2050];
char c;
struct node{
int val, len;
}a[2050];
vector<node> s;
void check(int r){
memset(a, 0, sizeof(a));
a[1].val = f[r][1];
a[1].len = 1;
while(!s.empty())s.pop_back();
for(int i = 1; i <= m; i++){
int width = 0;
while(!s.empty() && f[r][i] <= s.back().val){
width += s.back().len;
maxArea = max(maxArea, width * s.back().val);
s.pop_back();
}
a[i].val = f[r][i];
a[i].len = width + 1;
s.push_back(a[i]);
}
int width = 0;
while(!s.empty()){
width +=s.back().len;
maxArea = max(maxArea, width * s.back().val);
s.pop_back();
}
}
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> c;
if(c == 'F') f[i][j] = f[i - 1][j] + 1;
}
}
for(int i = 1; i<= n; i++)check(i);
cout << maxArea*3 << endl;
return 0;
}