题解:洛谷 AT_abc425_d Ulam-Warburton Automaton

【题目来源】

洛谷:AT_abc425_d [ABC425D] Ulam-Warburton Automaton - 洛谷

【题目描述】

有一个有 \(H\)\(W\) 列的网格。我们称自上而下第 \(i\)\((1 \leq i \leq H)\)、自左而右第 \(j\)\((1 \leq j \leq W)\) 交汇处的格子为 \((i,j)\)

初始时,如果 \(S_{i,j}\)#,则 \((i,j)\) 被染成黑色;如果为 .,则 \((i,j)\) 被染成白色。

执行下面的操作 \(10^{100}\) 次:

  • \(T\) 为所有仅有一个相邻黑色格子的白色格子的集合。将这些格子全部染成黑色。这里,两个格子 \((i_1, j_1)\)\((i_2, j_2)\) 相邻当且仅当 \(|i_1 - i_2| + |j_1 - j_2| = 1\)

请你求出所有操作完成后被染成黑色的格子的数量。

【输入】

输入从标准输入读取,格式如下:

\(H\ W\)
\(S_{1,1}S_{1,2}\ldots S_{1,W}\)
\(S_{2,1}S_{2,2}\ldots S_{2,W}\)
\(\vdots\)
\(S_{H,1}S_{H,2}\ldots S_{H,W}\)

【输出】

输出答案。

【输入样例】

9 9
.........
.........
.........
.........
....#....
.........
.........
.........
.........

【输出样例】

57

【核心思想】

  1. 问题分析:给定 \(H \times W\) 的网格,初始部分格子为黑色 #。每次操作将所有"恰好有 \(1\) 个相邻黑格"的白格染黑,重复 \(10^{100}\) 次。求最终黑格数量。这是一个BFS模拟/细胞自动机问题,关键在于发现操作具有"层状扩散"特性——每轮新染黑的格子只与上一轮新染黑的格子相邻,可用双队列BFS模拟逐层扩展过程,无需真的执行 \(10^{100}\) 次。

  2. 算法选择

    • BFS层序模拟:用两个队列 \(q_1\)(当前轮待染黑的格子)和 \(q_2\)(下一轮的候选格子)交替处理,模拟逐层扩散
    • 邻接计数器\(adj[i][j]\) 记录每个格子相邻的黑格数量,动态更新以避免每次重新计算
    • 提前终止:当某轮没有新格子被染黑(\(q_2\) 为空)时,过程已稳定,直接结束
  3. 关键步骤

    • 读取输入:读入 \(H, W\) 和网格 \(S\)
    • 初始化
      • \(col[i][j] = 1\)\(S_{i,j}\) = #,否则 \(0\)
      • 预处理 \(adj[i][j]\):遍历所有黑格,对其四邻域的 \(adj\) 值加 \(1\)
      • 将所有满足 \(adj[i][j] = 1\)\(col[i][j] = 0\) 的白格加入 \(q_1\)
    • BFS主循环
      • 处理 \(q_1\):弹出格子 \((x, y)\),染黑 \(col[x][y] = 1\),对其四邻域 \(adj\)\(1\);若邻域格子未访问且未入队,加入 \(q_2\)
      • 检查 \(q_2\):若为空则跳出循环(无新格子可染)
      • 筛选 \(q_2\):弹出格子,若仍为白且 \(adj = 1\),则标记为已访问并加入 \(q_1\);否则丢弃
    • 统计答案:遍历网格,累加所有 \(col[i][j] = 1\) 的格子数
  4. 时间/空间复杂度

    • 时间复杂度:\(O(H \cdot W)\),每个格子最多被处理一次,\(adj\) 更新为 \(O(1)\)
    • 空间复杂度:\(O(H \cdot W)\),网格、邻接计数器、访问标记及队列
  5. BFS层序模拟的核心思想

    • 局部规则驱动全局演化:每个格子的状态仅依赖其局部邻域,通过维护 \(adj\) 计数器实现 \(O(1)\) 状态转移
    • 层状扩散特性:每轮新染黑的格子形成"前沿面",只影响下一轮的候选集,与 \(10^{100}\) 次操作无关,过程在有限步内必然收敛
    • 双队列交替\(q_1\) 处理当前轮、\(q_2\) 收集下一轮候选,避免在同一轮中混淆不同阶段的格子
    • 惰性验证:格子先入 \(q_2\) 再验证条件,处理多邻域同时更新的竞态问题
    • 适用于"细胞自动机/局部规则迭代"类问题,核心是识别扩散的层状结构并用BFS模拟

【算法标签】

普及 #BFS-二维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 300005;  // 定义最大网格尺寸

typedef pair<int, int> PII;  // 坐标对类型

int h, w;                    // 网格的行数和列数
int ans;                     // 最终结果(被标记的格子数)

vector<int> col[N];          // 格子颜色(0:白,1:黑)
vector<int> adj[N];          // 记录每个格子相邻的黑格子数
vector<bool> vis[N];         // 标记是否已被处理
vector<bool> inq[N];         // 标记是否在队列中

queue<PII> q1, q2;           // 双队列用于BFS处理

// 四个移动方向(上、下、左、右)
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};

int main()
{
    // 输入网格尺寸
    cin >> h >> w;

    // 初始化二维数组
    for (int i = 1; i <= h; i++)
    {
        vis[i].resize(w + 1);
        col[i].resize(w + 1);
        adj[i].resize(w + 1);
        inq[i].resize(w + 1);
    }

    // 输入网格数据并预处理相邻关系
    for (int i = 1; i <= h; i++)
    {
        string s;
        cin >> s;
        s = " " + s;  // 调整为1-based索引

        for (int j = 1; j <= w; j++)
        {
            // 记录格子颜色('#'为黑,其他为白)
            col[i][j] = (s[j] == '#');

            // 预处理每个格子相邻的黑格子数
            for (int k = 0; k < 4; k++)
            {
                int nx = i + dx[k], ny = j + dy[k];
                if (nx < 1 || nx > h || ny < 1 || ny > w)
                {
                    continue;
                }
                adj[nx][ny] += col[i][j];
            }
        }
    }

    // 初始处理:找到所有满足条件的白格子
    for (int i = 1; i <= h; i++)
    {
        for (int j = 1; j <= w; j++)
        {
            if (adj[i][j] == 1 && col[i][j] == 0)
            {
                vis[i][j] = 1;
                q1.push({i, j});
            }
        }
    }

    // 主处理循环
    while (true)
    {
        // 处理第一队列中的格子
        while (!q1.empty())
        {
            int x = q1.front().first, y = q1.front().second;
            q1.pop();

            // 将当前格子标记为黑
            col[x][y] = 1;

            // 更新相邻格子的计数
            for (int k = 0; k < 4; k++)
            {
                int nx = x + dx[k], ny = y + dy[k];
                if (nx < 1 || nx > h || ny < 1 || ny > w)
                {
                    continue;
                }

                ++adj[nx][ny];  // 相邻格子黑格子数+1

                if (vis[nx][ny])
                {
                    continue;
                }

                if (!inq[nx][ny])
                {
                    q2.push({nx, ny});
                    inq[nx][ny] = 1;
                }
            }
        }

        // 如果第二队列为空,结束处理
        if (q2.empty())
        {
            break;
        }

        // 处理第二队列中的格子
        while (!q2.empty())
        {
            int x = q2.front().first, y = q2.front().second;
            q2.pop();
            inq[x][y] = 0;

            // 检查是否满足处理条件
            if (col[x][y] == 0 && adj[x][y] == 1)
            {
                vis[x][y] = 1;
                q1.push({x, y});
            }
        }
    }

    // 统计最终被标记为黑的格子数
    for (int i = 1; i <= h; i++)
    {
        for (int j = 1; j <= w; j++)
        {
            ans += col[i][j];
        }
    }

    // 输出结果
    cout << ans << endl;

    return 0;
}

【运行结果】

9 9
.........
.........
.........
.........
....#....
.........
.........
.........
.........
57
posted @ 2026-08-27 11:02  团爸讲算法  阅读(8)  评论(0)    收藏  举报