1. 程式人生 > 其它 >【題解】AcWing 1387.家的範圍

【題解】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的長度的最小值

  1. 左上部,容易得到是f[i - 1][j - 1]
  2. 左部,是f[i][j - 1]([i, j-1] 開始往左/往上的1的長度)(我們想要用的是往左,因為所要求的是正方形,所以往上的是和情況1重複了,但是因為是取最值,所以沒有影響,情況3同理
  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;
}