题解:洛谷 P8628 穿越雷区
【题目来源】
洛谷:P8628 [蓝桥杯 2015 国 AC] 穿越雷区 - 洛谷
【题目描述】
\(X\) 星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废。
某坦克需要从 \(A\) 区到 \(B\) 区去(\(A\),\(B\) 区本身是安全区,没有正能量或负能量特征),怎样走才能路径最短?
已知的地图是一个方阵,上面用字母标出了 \(A\),\(B\) 区,其它区都标了正号或负号分别表示正负能量辐射区。
例如:
A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -
坦克车只能水平或垂直方向上移动到相邻的区。
【输入】
输入第一行是一个整数 \(n\),表示方阵的大小,\(4 \le n<100\)。
接下来是 \(n\) 行,每行有 \(n\) 个数据,可能是 A,B,+,- 中的某一个,中间用空格分开。
【输出】
要求输出一个整数,表示坦克从 \(A\) 区到 \(B\) 区的最少移动步数。
如果没有方案,则输出 \(-1\)。
【输入样例】
5
A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -
【输出样例】
10
【核心思想】
-
问题分析:坦克需要从 \(A\) 区到 \(B\) 区,必须交替经过正能量辐射区(
+)和负能量辐射区(-),求最短路径。这是一个状态扩展BFS问题,关键在于路径有额外约束条件,需要扩展状态维度来记录必要信息。 -
算法选择:
- 状态扩展BFS:使用三维状态
dist[x][y][last],表示到达 \((x, y)\) 且上一步经过的格子类型为last(0 表示+,1 表示-)的最短距离 - 状态转移:根据当前状态的
last值,决定下一步可以走哪种类型的格子 - 终点特殊处理:终点 \(B\) 可以接受任意类型的上一步
- 状态扩展BFS:使用三维状态
-
关键步骤:
- 初始化:读取 \(n\)(网格大小)和网格,记录起点 \(A\) 和终点 \(B\) 的坐标
- 初始化队列:从起点 \(A\) 出发,向四个方向扩展,根据邻居格子类型(
+或-)初始化dist数组并入队 - BFS扩展:
- 对于每个状态,计算下一步需要的格子类型
need = 1 - last - 只有当邻居格子类型等于
need(或为终点 \(B\))时才能转移 - 更新新状态的
nlast值并入队
- 对于每个状态,计算下一步需要的格子类型
- 答案提取:取终点在两种状态下的最小距离
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2 \times 2)\),每个格子最多访问两次(两种状态)
- 空间复杂度:\(O(n^2 \times 2)\),需要存储三维距离数组
-
状态扩展BFS的核心思想:
- 状态设计:当路径有额外约束条件时,需要扩展状态维度来记录必要信息
- 约束转化:本题中"上一步的类型"决定了"下一步能走哪里",因此需要将其纳入状态
- 多维距离数组:
dist[x][y][last]同时记录位置和状态信息 - 状态转移条件:根据当前状态决定哪些转移是合法的
- 适用于有状态约束的最短路径问题
【算法标签】
普及 #BFS-二维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, ans; // n: 网格大小,ans: 答案
char a[N][N]; // 网格
int dist[N][N][2]; // 距离数组,第三维表示上一步的行走方向
int stx, sty, edx, edy; // 起点和终点坐标
struct Node
{
int x, y, last; // 坐标和上一步的行走方向
};
int dx[4] = {-1, 1, 0, 0}; // 方向偏移
int dy[4] = {0, 0, -1, 1};
void bfs() // 广度优先搜索
{
memset(dist, -1, sizeof(dist)); // 初始化距离为-1
queue<Node> q; // 创建队列
dist[stx][sty][0] = dist[stx][sty][1] = 0; // 起点距离为0
for (int i = 0; i < 4; i++) // 初始化起点周围的点
{
int nx = stx + dx[i], ny = sty + dy[i];
if (nx < 1 || nx > n || ny < 1 || ny > n) // 越界检查
continue;
if (a[nx][ny] == '+') // 如果下一个格子是'+'
{
dist[nx][ny][0] = 1; // 记录距离
q.push({nx, ny, 0}); // 入队
}
else if (a[nx][ny] == '-') // 如果下一个格子是'-'
{
dist[nx][ny][1] = 1; // 记录距离
q.push({nx, ny, 1}); // 入队
}
}
while (!q.empty()) // 当队列不为空
{
int x = q.front().x, y = q.front().y, last = q.front().last; // 出队
q.pop();
if (x == edx && y == edy) // 如果到达终点
return; // 返回
for (int i = 0; i < 4; i++) // 遍历四个方向
{
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || nx > n || ny < 1 || ny > n) // 越界检查
continue;
int need = 1 - last; // 需要的格子类型
int nlast; // 新的last值
bool flag = false; // 标记是否可走
if (a[nx][ny] == 'B') // 如果下一个格子是终点
{
flag = true;
nlast = last; // 保持不变
}
else if (need == 0 && a[nx][ny] == '+') // 如果需要的类型是0且格子是'+'
{
flag = true;
nlast = 0; // 更新last
}
else if (need == 1 && a[nx][ny] == '-') // 如果需要的类型是1且格子是'-'
{
flag = true;
nlast = 1; // 更新last
}
if (flag && dist[nx][ny][nlast] == -1) // 如果可走且未访问
{
dist[nx][ny][nlast] = dist[x][y][last] + 1; // 更新距离
q.push({nx, ny, nlast}); // 入队
}
}
}
}
int main()
{
cin >> n; // 输入网格大小
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
{
cin >> a[i][j]; // 输入网格
if (a[i][j] == 'A') // 记录起点
stx = i, sty = j;
if (a[i][j] == 'B') // 记录终点
edx = i, edy = j;
}
bfs(); // 执行广度优先搜索
ans = -1; // 初始化答案
if (dist[edx][edy][0] != -1) // 如果终点在last=0时可达
ans = dist[edx][edy][0];
if (dist[edx][edy][1] != -1 && (ans == -1 || dist[edx][edy][1] < ans)) // 如果终点在last=1时可达且距离更短
ans = dist[edx][edy][1];
cout << ans << endl; // 输出答案
return 0;
}
【运行结果】
5
A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -
10
浙公网安备 33010602011771号