【题解】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;
}