• 【题解】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;
    }
    
  • 相关阅读:
    git拉取代码命令
    zookeeper(version3.6.3)安装使用《一》
    kafka(version2.1.3)安装<一>
    linux安装jdk
    linux安装支持文件上传下载的命令
    mapstruct 再也不用set不同的属性而劳累了
    RabbitMq确认消费,与重复消费避免使用冥等
    java8之非重入锁StampedLock ,并发的另一种处理方式
    微信Jssdk
    Vue-Router 的params和query传参两种方式
  • 原文地址:https://www.cnblogs.com/RemnantDreammm/p/15127215.html
Copyright © 2020-2023  润新知