题解:洛谷 P9944 Comfortable Cows

【题目来源】

洛谷:P9944 [USACO21FEB] Comfortable Cows B - 洛谷

【题目描述】

Farmer John 的草地可以被看作是一个由正方形方格组成的巨大的二维方阵(想象一个巨大的棋盘)。初始时,草地上是空的。

Farmer John 将会逐一地将 \(N\)\(1\le N\le 10^5\))头奶牛加入到草地上。第 \(i\) 头奶牛将会占据方格 \((x_i,y_i)\),不同于所有已经被其他奶牛占据的方格(\(0\le x_i,y_i\le 1000\))。

一头奶牛被称为是「舒适的」,如果它水平或竖直方向上与恰好三头其他奶牛相邻。Farmer John 对他的农场上舒适的奶牛数量感兴趣。对 \(1\ldots N\) 中的每一个 \(i\),输出第 \(i\) 头奶牛加入到草地上之后舒适的奶牛的数量。

【输入】

输入的第一行包含一个整数 \(N\)。以下 \(N\) 行每行包含两个空格分隔的整数,表示一头奶牛所在的方格坐标 \((x,y)\)。输入保证所有方格的坐标是不同的。

【输出】

输出的第 \(i\) 行包含前 \(i\) 头奶牛加入到草地上之后舒适的奶牛的数量。

【输入样例】

8
0 1
1 0
1 1
1 2
2 1
2 2
3 1
3 2

【输出样例】

0
0
0
1
0
0
1
2

【核心思想】

  1. 问题分析:在一个无限大的网格上,逐次添加奶牛(坐标不重复)。每次添加后,需要统计当前所有奶牛中,恰好有3个四连通邻居的奶牛数量。每次只新增一头奶牛,只会影响该奶牛自身及其上下左右四个邻居的“舒适状态”。因此无需每次全局扫描,只需增量更新受影响的5个位置,维护全局舒适奶牛总数。

  2. 算法选择

    • 增量模拟:用一个二维数组 a 标记奶牛位置(坐标范围 \(0 \le x,y \le 1000\),但可适当扩展),用全局变量 res 记录当前舒适奶牛总数。
    • 局部更新:每添加一头新奶牛 (x, y) 时,只检查该点及其上下左右四个邻居(最多5个位置)在添加前后的舒适状态变化,将变化量累加到 res 中。
    • 辅助函数:定义一个函数 isComfortable(x, y),返回坐标 (x, y) 处的奶牛是否舒适(计算其四邻居中奶牛的数量是否等于3)。这样可以避免重复代码。
  3. 关键步骤

    • 初始化:读入 \(N\),清空标记数组 ares = 0
    • 处理每头新奶牛(坐标 (x, y)):
      • 添加前的检查:遍历受影响位置集合 P = {(x,y), (x-1,y), (x+1,y), (x,y-1), (x,y+1)},对其中每个有奶牛的位置 p,若 isComfortable(p) 为真,则 res--(因为这些位置在添加新牛后可能不再舒适)。
      • 添加新牛a[x][y] = 1
      • 添加后的检查:再次遍历同样的集合 P,对其中每个有奶牛的位置 p,若 isComfortable(p) 为真,则 res++
      • 输出当前 res
    • isComfortable(x, y) 函数:检查四个方向 (x+dx[k], y+dy[k]) 是否在网格内且 a 为1,统计数量,返回是否等于3。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每头奶牛只检查常数个(≤5)位置,每个位置检查4个邻居,\(N \le 10^5\),完全可行。
    • 空间复杂度:\(O(C^2)\),其中 \(C\) 为坐标范围上限(约1005×1005),也可用哈希表存储奶牛集合以优化空间,但二维数组足够。
  5. 增量更新 + 局部影响分析的核心思想

    • 局部性:新增一头奶牛只会改变其自身及其四邻居的邻居数量,进而只可能改变这5个位置的舒适状态。利用这一性质,将全局问题转化为常数规模的局部更新。
    • 状态修正:先减去受影响的旧状态,再添加新奶牛,最后加上受影响的新状态,等价于只计算净变化量,避免了每次全局重算。
    • 边界处理:坐标范围可能为0,检查邻居时需确保不越界。
    • 适用场景:适用于动态添加元素、且状态仅依赖局部邻居关系的网格模拟问题。核心是通过维护受影响的小范围集合实现 \(O(1)\) 更新。

【解题思路】

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, res=0, cnt=0;
int a[1005][1005];
int dx[4] = {-1,0,1,0}, dy[4] = {0,1,0,-1};
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 遍历n头奶牛
        int x, y;  // 定义每头奶牛的坐标x和y
        int cnt1=0, cnt2=0;  // 定义添加前和添加后舒适量的统计
        cin >> x >> y;  // 输入奶头的坐标
        for (int i=x-1; i<=x+1; i++) {  // 遍历这台奶牛周围,计算添加之前的舒适量
            for (int j=y-1; j<=y+1; j++) {
                int ans=0;  // 定义周围奶牛的数量,初始为0
                if (a[i][j]==1) {  // 如果该位置有奶牛
                    for (int k=0; k<4; k++) {  // 则判断周围4个方向是否有奶牛
                        int xx = i + dx[k];
                        int yy = j + dy[k];
                        if (xx<0 || yy<0) continue;
                        if (a[xx][yy]==1) ans++;  // 有的话,ans自增
                    }
                    if (ans==3) cnt1++;  // 如果ans为3,则舒适的奶牛数量加1
                }
            }
        }
        a[x][y] = 1;  // 添加输入的奶牛
        for (int i=x-1; i<=x+1; i++) {  // 再次遍历这台奶牛周围,计算添加之后的舒适量
            for (int j=y-1; j<=y+1; j++) {
                int ans=0;
                if (a[i][j]==1) {
                    for (int k=0; k<4; k++) {
                        int xx = i + dx[k];
                        int yy = j + dy[k];
                        if (xx<0 || yy<0) continue;
                        if (a[xx][yy]==1) ans++;
                    }
                    if (ans==3) cnt2++;  // 如果ans为3,则舒适的奶牛数量加1
                }
            }
        }
        res += cnt2-cnt1;  // 计算前后的变化,并累加到ans中(有时相减会变为负数)
        cout << res << endl;  // 最后打印结果
    }
    return 0;
}

【运行结果】

8
0 1
0
1 0
0
1 1
0
1 2
1
2 1
0
2 2
0
3 1
1
3 2
2
posted @ 2026-08-29 09:41  团爸讲算法  阅读(4)  评论(0)    收藏  举报