题解:洛谷 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
【核心思想】
-
问题分析:给定 \(H \times W\) 的网格,初始部分格子为黑色
#。每次操作将所有"恰好有 \(1\) 个相邻黑格"的白格染黑,重复 \(10^{100}\) 次。求最终黑格数量。这是一个BFS模拟/细胞自动机问题,关键在于发现操作具有"层状扩散"特性——每轮新染黑的格子只与上一轮新染黑的格子相邻,可用双队列BFS模拟逐层扩展过程,无需真的执行 \(10^{100}\) 次。 -
算法选择:
- BFS层序模拟:用两个队列 \(q_1\)(当前轮待染黑的格子)和 \(q_2\)(下一轮的候选格子)交替处理,模拟逐层扩散
- 邻接计数器:\(adj[i][j]\) 记录每个格子相邻的黑格数量,动态更新以避免每次重新计算
- 提前终止:当某轮没有新格子被染黑(\(q_2\) 为空)时,过程已稳定,直接结束
-
关键步骤:
- 读取输入:读入 \(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\)
- \(col[i][j] = 1\) 若 \(S_{i,j}\) =
- 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\) 的格子数
-
时间/空间复杂度:
- 时间复杂度:\(O(H \cdot W)\),每个格子最多被处理一次,\(adj\) 更新为 \(O(1)\)
- 空间复杂度:\(O(H \cdot W)\),网格、邻接计数器、访问标记及队列
-
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
浙公网安备 33010602011771号