Difficulty:
TCGS-b027: ⭐️⭐️
TIOJ-1603: ⭐️⭐️⭐️
# Problem Description 題敘:
TCGS-b027 Link
TIOJ-1063 Link
兩題類似題,放在一起。
TCGS-b027: 給一個二維陣列,求全部由 組合成的正方形區域的最大面積有多少?
TIOJ-1063: 給一個二維陣列,求全部由 組成的最大矩形面積有多少?
# Thinking 思考歷程:
TCGS-b027:
如果暴力硬做的話鐵定不行,那怎麼辦?如果掃到  這個數字是鐵定,不可能由他構成正方形的。既然知道是  ,那就必須想辦法利用之前算過的答案來得到這個解。那如果我依序把矩陣掃過去的話,那麼在已經算過的解當中,在我們的左方或上方的解應該會跟當前這項的解有比較大的關系 ( 是正方形嘛)。 朝這個方向想答案就出來了~
TIOJ-1063:
這題把  換成  。然後這題似乎再比剛剛那題難一點點,因為這次的形狀只要符合矩形的定義就好,如果只用正方形的算法,肯定會少算。那我嘗試作些改變,若對於一個當前項,把往左邊、與往上看的值記成一個  而非取  呢?好像也沒有太大的作用耶。怎麼辦?想好久還是想不到... 😢 只好去看題解。
# Solution 題解:
TCGS-b027:
由以上的思路,如果我這項是  ,才有可能會創造出更大的正方形。我往左 & 往上看之前算過的解,先看看左邊鄰居,若以左邊鄰居作為一個正方形右下角的邊長,最大會是多少;再來看看樓上的鄰居,若樓上的鄰居作為另一個正方形右下角的邊長,最大會是多少。兩個值取  再  就是以當前的這項為右下角的新最大正方形邊長。之後每算完一次就和目前的最大值 ,最後的  再平方就結束了。
TIOJ-1063:
要構成一個矩形的條件,首先就是要確定長或寬其中一個邊。往這個方向想,我們用兩個 for 照順序把矩陣掃過去,當掃到第 j 行,用一個 len[j] 來記錄由 j 往上看,最多有幾個一。(這步  我真的想不到) 如果我當前的元素是  的話就把 len[j] 這列歸零,否則就是 上一行掃的這列的數量  。這暫且確定了直向的長度。那在這裡先偷偷把橫向的長度當作一,取一次 。之後把 mn 先設成 len[j] 當作 當前的直向最小值,在此也初始一個變數 k 為 j-1 ,這兩個等等會用到。
現在來處理橫向的。由我們的 j 由後往前掃來過展我們橫向邊,一直拓展到最左邊或遇到  。但是,如果要拓展橫向到 k 的話,那其實直向長度 len[k] 有可能更長或更短,那為了符合需求,只能將就取最短的。這個時候我們的 mn 就派上用場了。 mn = min(mn, len[k]) 紀錄目前的直向最小值,有了這個以後,我們就能安心的往作擴展,並把 k-1 * (j-k) 在和 mx  一下就是目前的最大矩形面積。(之所以要把 k 減掉  是因為我們既然已經  過 len[k] ,所以橫向的長度必須要包括他。如果這個不好理解的話,可以想想開始初始條件:我們以經偷偷算過橫向長度為  的解了,而 k 又從 j-1 開始遞減,如果直接拿 k-(j-1) = 1 肯定錯)
# AC Code 程式碼:
TCGS-b027:
| #include <iostream> | |
| typedef long long LL; | |
| #define IOS ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0); | |
| using namespace std; | |
| const int N = 1e2; | |
| int dp[N][N] = {}, arr[N][N]; | |
| int main(){ | |
| IOS; int h,w; cin>>h>>w; | |
| for(int i = 0; i<h; i++){ | |
| for(int j = 0; j <w; j++){ | |
| cin>>arr[i][j]; | |
| 		} | |
|     } | |
| int mx = 0; | |
| for(int i = 1; i<h; i++){ | |
| for(int j = 1; j<w; j++){ | |
| if(arr[i][j] == 1) continue; | |
| dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1]+1); | |
| mx = max(mx, dp[i][j]); | |
| 		} | |
| 	} | |
| cout<<mx*mx<<'\n'; | |
| } | 
TIOJ-1063: (參考資料)
| #include <bits/stdc++.h> | |
| using namespace std; | |
| const int N = 205; | |
| int arr[N][N] = {}, len[N] = {}; | |
| int main(){ | |
| ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); | |
| int m,n; cin>>m>>n; | |
| for(int i = 0; i<m; i++) for(int j = 0; j<n; j++) cin>>arr[i][j]; | |
| int mx = 0; | |
| for(int i = 0; i<m; i++){ | |
| for(int j = 0; j<n; j++){ | |
| len[j] = (arr[i][j] ? len[j] + 1 : 0); // 最長的直向 | |
| mx = max(mx, len[j] * 1); | |
| int k = j - 1; | |
| int mn = len[j]; | |
| if(len[j] == 0) continue; | |
| while(k>=0 and arr[i][j]){ | |
| mn = min(mn, len[k]); // 新橫向 | |
| k--; | |
| mx = max(mx, mn*(j-k)); | |
|             } | |
|         } | |
|     } | |
| cout<<mx<<'\n'; | |
| } | 
NOTE: 第 16 行可以直接 continue 其實是因為如果本身是  的話其實沒甚麼好拓展的,答案都是  ,因此在某些條件下能省一些時間。
<br/>
# 學習到的新技巧 or 概念~
- TIOJ-1063: 連續元素的
