【題解】AcWing 1387.家的範圍
題目描述
農夫約翰在一片邊長為 N 英里的正方形土地中放牛。
它的牛隻能在這片土地裡吃草。
這片土地可以看作是一個 N×N 的方格矩陣。
其中一部分方格區域的土地已經被破壞了。
現在約翰想要統計目前還有多少個可以用來放牧的正方形區域土地。
邊長大於 2 且內部完好無損的正方形土地被視為可用來放牧的土地。
在統計可用來放牧的不同正方形區域的個數時,一些方格區域可以被重複考慮。
換句話說,統計到的兩個不同的可放牧的正方形區域之間可以存在重疊部分。
輸入格式
第一行包含一個整數 N。
接下來 N 行,每行包含一個長度為 N 的 01 字串,它們共同表現出了整片正方形土地的現狀。
其中,0 表示被破壞的土地,1 表示完好的土地。
輸出格式
找出所有可放牧正方形區域,並按照它們的邊長進行歸類和輸出。
在輸出時,每行輸出兩個整數,第一個整數表示一種可放牧正方形區域的邊長,第二個整數表示這種邊長的可放牧正方形區域的數量。
輸出時,按邊長從小到大的順序依次輸出。
資料範圍
2≤N≤250,
輸入樣例:
6
101111
001111
111111
001111
101101
111001
輸出樣例:
2 10
3 4
4 1
(DP) \(O(n^3)\)
思路
根據資料範圍,如果暴力列舉,250 * 250 * 1 + 249 * 249 * 4 * ... + 1 * 1 * 250 * 250,時間會爆掉
面對這個情況,我們也不能想到資料結構來做優化,所以想到DP
首先,暴力列舉的時候,會重複驗證一個點是'1'還是'0',所以可以通過DP來消除重複列舉的冗餘。
然後,我們需要構造DP的狀態了。
首先,我們考慮到狀態的轉移是要通過格子之間的關係,所以,每個格子以其為右下角可構成的正方形的邊長記錄下來,因為當邊長可以為x時,邊長一定可以為x-1,所以我們可以只記錄其最大值。
得出狀態表示:f[i][j], 以i, j為右下角的能構造出的最大的正方形的邊長
後面我們思考狀態轉移,
0110
1111
1111
1011
以這個為例,
ps. i = 4, j = 4
我們想要得到f[i][j],我們需要知道i, j往內有多少個連續的1,這邊因為記錄的是邊長,所以也可以把這個邊長當作其往上/左連續1的長度的最小值
- 左上部,容易得到是f[i - 1][j - 1]
- 左部,是f[i][j - 1]([i, j-1] 開始往左/往上的1的長度)(我們想要用的是往左,因為所要求的是正方形,所以往上的是和情況1重複了,但是因為是取最值,所以沒有影響,情況3同理
- 上部,是f[i - 1][j]
因為都要是1,所以在三種情況中取min在加1
當然,i, j 本身當然要是1
狀態轉移:f[i][j] = \(min(f[i - 1][j - 1], f[i - 1][j], f[i][j - 1]) + 1\)
時間複雜度
列舉右下角是哪個格子\(O(n^2)\)i
統計以該格子為右下角對cnt[i]做出的貢獻\(O(n)\)
總計\(O(n^3)\)
參考文獻
C++ 程式碼
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 260;
int n, maxn;
int cnt[N], f[N][N]; // [i, j] as right_down point, max (a)
char g[N][N];
/*
* (g[i][j] == '1') f[i][j] = min(f[i - 1][j - 1], f[i][j - 1], f[i][j - 1]) + 1;
*/
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++) cin >> g[i] + 1;
for(int i = 1; i <= n; i ++) // no need for initialization
for(int j = 1; j <= n; j ++)
if(g[i][j] == '1')
f[i][j] = min(f[i - 1][j - 1], min(f[i][j - 1], f[i - 1][j])) + 1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
{
maxn = max(maxn, f[i][j]);
for(int k = 1; k <= f[i][j]; k ++)
cnt[k] ++;
}
for(int i = 2; i <= maxn; i ++)
cout << i << ' ' << cnt[i] << endl;
return 0;
}