题解:洛谷 P2960 Invasion of the Milkweed
【题目来源】
洛谷:P2960 [USACO09OCT] Invasion of the Milkweed G - 洛谷
【题目描述】
农夫约翰一直尽力保持牧场里长满丰盛、美味且健康的草供奶牛食用。然而,他输掉了这场战斗,因为邪恶的乳草在他的农场西北部站稳了脚跟。
牧场通常被划分为一个直角网格,高度为 \(Y\)(\(1 \le Y \le 100\)),宽度为 \(X\)(\(1 \le X \le 100\)),其中 \((1,1)\) 位于左下角(即,排列为正常的 \(X,Y\) 坐标网格)。乳草最初开始在方格 \((M_x,M_y)\) 生长。每周,乳草会传播到它已经占据的任何方格周围的所有非岩石方格,最多可以传播到八个方格(包括直角方格和对角线方格)。在这些方格中仅仅一周后,它就准备好继续传播到更多方格。
贝茜想在牧场被乳草占领之前尽可能多地享受青草。她想知道牧场能持续多久。如果乳草在时间零时位于方格 \((M_x,M_y)\),那么它在何时完成对牧场的入侵(对于给定的输入数据,这种情况总会发生)?
牧场由一个图示描述,'.' 代表草,'*' 代表巨石,如下例所示,\(X=4\),\(Y=3\):
....
..*.
.**.
如果乳草从左下角开始(行=1,列=1),那么地图将按如下方式演变:
.... .... MMM. MMMM MMMM
..*. MM*. MM*. MM*M MM*M
M**. M**. M**. M**. M**M
week 0 1 2 3 4
乳草在 4 周后占领了整个牧场。
【输入】
* 第 1 行:四个以空格分隔的整数:\(X\),\(Y\),\(M_x\) 和 \(M_y\)
* 第 2 行到第 \(Y+1\) 行:第 \(y + 1\) 行描述了牧场的第 \((Y + 1 - y)\) 行,其中包含 \(X\) 个字符('.' 代表草,'*' 代表巨石)
【输出】
* 第 1 行:一个整数,表示乳草占领牧场最后一个非巨石方格的周数。
【输入样例】
4 3 1 1
....
..*.
.**.
【输出样例】
4
【核心思想】
-
问题分析:给定 \(X \times Y\) 的网格,乳草从 \((M_x, M_y)\) 开始,每周向八邻域(上下左右及四个对角线)的非岩石格子传播。求乳草占领最后一个非岩石格子所需的周数。这是一个八方向BFS/洪水填充问题,关键在于正确处理八邻域扩展和坐标系转换。
-
算法选择:
- BFS层序扩展:乳草每周向外扩展一层,BFS的层数恰好对应周数
- 八方向搜索:上下左右 + 四个对角线,共 \(8\) 个方向
- 坐标系转换:题目中 \((1,1)\) 在左下角,而数组通常从上到下存储,需要行坐标转换
-
关键步骤:
- 读取输入:\(X, Y, M_x, M_y\) 和网格地图
- 坐标转换:输入行坐标 \(x\) 转换为数组行索引 \(x = n - x + 1\)(翻转行顺序)
- BFS初始化:起点 \((x, y)\) 入队,\(step = 0\),标记访问
- BFS扩展:
- 取出队首 \((xx, yy, step)\)
- 遍历 \(8\) 个方向 \((nx, ny)\):
- 边界检查、访问检查、岩石检查(
'*') - 若合法:标记访问,入队 \((nx, ny, step+1)\),\(ans = \max(ans, step+1)\)
- 边界检查、访问检查、岩石检查(
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(X \cdot Y)\),每个格子最多入队一次
- 空间复杂度:\(O(X \cdot Y)\),访问标记和队列
-
八方向BFS的核心思想:
- 层数=时间:BFS每扩展一层对应一周,最后访问的格子的 \(step\) 即为总周数
- 八连通性:包含对角线方向的传播,比四方向BFS多 \(4\) 个扩展方向
- 坐标系映射:题目使用左下角为原点的坐标系,通过 \(n - x + 1\) 转换为数组的从上到下索引
- 洪水填充:从起点开始逐步"淹没"所有可达区域,记录最后淹没的位置的时间
- 适用于"网格扩散 + 时间计算"类问题,核心是BFS层序扩展和正确的方向/坐标处理
【算法标签】
普及 #BFS-二维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 105; // 网格最大尺寸
int n, m, x, y, ans; // n: 行数,m: 列数,x,y: 起始坐标,ans: 最大步数
char a[N][N]; // 网格地图
bool vis[N][N]; // 访问标记数组
int dx[8] = {-1,-1,-1,0,0,1,1,1}; // 8个方向的行偏移量
int dy[8] = {-1,0,1,-1,1,-1,0,1}; // 8个方向的列偏移量
struct Node
{
int x, y, step; // 节点坐标和步数
};
void bfs()
{
queue<Node> q;
q.push({x, y, 0}); // 起点入队
vis[x][y] = 1; // 标记起点已访问
while (!q.empty())
{
int xx = q.front().x, yy = q.front().y, step = q.front().step;
q.pop();
// 遍历8个方向
for (int i = 0; i < 8; i++)
{
int nx = xx + dx[i], ny = yy + dy[i]; // 计算新坐标
// 检查边界
if (nx < 1 || nx > n || ny < 1 || ny > m) continue;
// 检查是否已访问
if (vis[nx][ny]) continue;
// 检查是否为障碍
if (a[nx][ny] == '*') continue;
// 标记访问并加入队列
vis[nx][ny] = 1;
q.push({nx, ny, step + 1});
ans = max(ans, step + 1); // 更新最大步数
}
}
}
int main()
{
// 读入网格尺寸和起始坐标
// 注意输入顺序:先列数m,再行数n,再列坐标y,再行坐标x
cin >> m >> n >> y >> x;
// 调整行坐标(题目坐标系与数组索引的转换)
x = n - x + 1; // 将输入的行坐标转换为数组索引
// 读入网格地图
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
}
}
// 标记起点为已访问状态
a[x][y] = '#';
// 开始广度优先搜索
bfs();
// 输出从起点出发能到达的最远步数
cout << ans << endl;
return 0;
}
【运行结果】
4 3 1 1
....
..*.
.**.
4
浙公网安备 33010602011771号