题解:洛谷 P2701 巨大的牛棚

【题目来源】

洛谷:P2701 [USACO5.3] 巨大的牛棚 Big Barn

【题目描述】

FJ 有一个大小为 \(n\times n\) 的农场(\(1\le n\le 1000\)),他想要在他的农场上建造一座正方形大牛棚。他的农场中有 \(t\) 棵果树(\(1\le t\le10000\)),但他为了不破坏果树,就想找一个空旷无树的地方修建牛棚。你的任务是计算并输出,在他的农场中,不需要砍树却能够修建的最大正方形牛棚的边长。当然,牛棚的边必须和水平轴和垂直轴平行。

考虑下面的农场,. 表示没有树的方格,# 表示有树的方格。

0 1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . # . . . # . .
3 . . . . . . . .
4 . . . . . . . .
5 . . . . . . . .
6 . . # . . . . .
7 . . . . . . . .
8 . . . . . . . .

最大的牛棚是边长为 \(5\) 的,可以建造在农场右下角的两个位置其中一个。

【输入】

\(1\) 行输入两个正整数 \(n\)\(t\)

\(2\sim t+1\) 行输入两个正整数 \(x,y\ (1\le x,y\le n)\)

【输出】

只由一行组成,约翰的牛棚的最大边长。

【输入样例】

8 3
2 2
2 6
6 3

【输出样例】

5

【核心思想】

  1. 问题分析:给定 \(n \times n\) 的网格,其中有 \(t\) 个位置有树(障碍物),求边与坐标轴平行的最大正方形空区域(不含树)的边长。这是一个二维 DP 问题,关键在于状态设计能递推地利用子问题的最优解。

  2. 算法选择

    • 二维动态规划:设 \(dp[i][j]\) 表示以 \((i, j)\) 为右下角的最大无树正方形边长
    • 状态转移:当前位置能扩展的正方形边长受限于上方、左方、左上方三个相邻位置的最小值
  3. 关键步骤

    • 初始化:读取 \(n\)(农场边长)、\(t\)(果树数量),标记有树的位置 \(g[x][y] = 1\)
    • DP 数组初始化\(dp[i][j]\) 初始化为较大值(边界处理),\(ans = 0\)
    • 递推计算(遍历 \(i\)\(1\)\(n\)\(j\)\(1\)\(n\)):
      • \(g[i][j] = 1\)(有树):\(dp[i][j] = 0\)
      • \(i = 1\)\(j = 1\)(边界):\(dp[i][j] = 1\)(无树位置)
      • 否则:状态转移 \(dp[i][j] = \min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1\)
      • 更新全局答案 \(ans = \max(ans, dp[i][j])\)
    • 输出答案 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^2)\),双层循环遍历整个网格
    • 空间复杂度:\(O(n^2)\)\(dp\) 二维数组和 \(g\) 标记数组
  5. 二维 DP 的核心思想

    • 子结构重叠:以 \((i, j)\) 为右下角的正方形,其边长受限于三个方向(上、左、左上)能形成的最小正方形,因为这三个方向必须同时满足无树才能扩展
    • 最小值约束\(dp[i][j]\) 取三者最小值加 \(1\),因为只要任一方向存在树或边界限制,当前正方形就无法突破该限制
    • 边界处理:第一行和第一列的无树位置最大只能形成 \(1 \times 1\) 的正方形,作为递推基础
    • 贪心最优性:每个位置记录以它为右下角的最大正方形,全局取最大即得答案
    • 适用于最大正方形/矩形、矩阵覆盖、障碍物规避类问题

【算法标签】

普及 #线性DP-二维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 1005; // 最大农场尺寸
int n, t, ans; // n:农场边长, t:果树数量, ans:最大正方形牛棚边长
int g[N][N]; // g[i][j]:标记该位置是否有树(1表示有树,0表示无树)
int dp[N][N]; // dp[i][j]:以(i,j)为右下角的最大无树正方形边长

int main()
{
    cin >> n >> t; // 读入农场边长和果树数量
    for (int i=1; i<=t; i++) // 读入每棵果树的位置
    {
        int x, y; cin >> x >> y;
        g[x][y] = 1; // 标记该位置有树
    }
    // 初始化dp数组为较大值(用于边界处理)
    memset(dp, 0x3f, sizeof(dp));
    for (int i=1; i<=n; i++) // 外层循环:枚举行
        for (int j=1; j<=n; j++) // 内层循环:枚举列
        {
            if (g[i][j]) // 如果当前位置有树
            {
                dp[i][j] = 0; // 以该位置为右下角的正方形边长为0(不能建牛棚)
            }
            else if (i==1 || j==1) // 边界位置(第一行或第一列)
                dp[i][j] = 1; // 边界上无树的位置最大正方形边长为1
            else
            {
                // 状态转移:以(i,j)为右下角的最大正方形边长
                // 取决于上方、左方、左上方三个位置的最小值加1
                // 原理:如果这三个方向都能形成边长为k的正方形,则当前可形成边长为k+1的正方形
                dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]})+1;
            }
            ans = max(ans, dp[i][j]); // 更新全局最大边长
        }
    cout << ans << endl; // 输出最大正方形牛棚的边长
    return 0;
}

【运行结果】

8 3
2 2
2 6
6 3
5
posted @ 2026-08-10 12:28  团爸讲算法  阅读(7)  评论(0)    收藏  举报