【题解】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;
}
posted @ 2021-08-11 10:48  Livinfly  阅读(36)  评论(0编辑  收藏  举报