题解:洛谷 P8628 穿越雷区

【题目来源】

洛谷:P8628 [蓝桥杯 2015 国 AC] 穿越雷区 - 洛谷

【题目描述】

\(X\) 星的坦克战车很奇怪,它必须交替地穿越正能量辐射区和负能量辐射区才能保持正常运转,否则将报废。

某坦克需要从 \(A\) 区到 \(B\) 区去(\(A\)\(B\) 区本身是安全区,没有正能量或负能量特征),怎样走才能路径最短?

已知的地图是一个方阵,上面用字母标出了 \(A\)\(B\) 区,其它区都标了正号或负号分别表示正负能量辐射区。

例如:

A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

坦克车只能水平或垂直方向上移动到相邻的区。

【输入】

输入第一行是一个整数 \(n\),表示方阵的大小,\(4 \le n<100\)

接下来是 \(n\) 行,每行有 \(n\) 个数据,可能是 AB+- 中的某一个,中间用空格分开。

【输出】

要求输出一个整数,表示坦克从 \(A\) 区到 \(B\) 区的最少移动步数。

如果没有方案,则输出 \(-1\)

【输入样例】

5
A + - + -
- + - - +
- + + + -
+ - + - +
B + - + -

【输出样例】

10

【核心思想】

  1. 问题分析:坦克需要从 \(A\) 区到 \(B\) 区,必须交替经过正能量辐射区(+)和负能量辐射区(-),求最短路径。这是一个状态扩展BFS问题,关键在于路径有额外约束条件,需要扩展状态维度来记录必要信息。

  2. 算法选择

    • 状态扩展BFS:使用三维状态 dist[x][y][last],表示到达 \((x, y)\) 且上一步经过的格子类型为 last(0 表示 +,1 表示 -)的最短距离
    • 状态转移:根据当前状态的 last 值,决定下一步可以走哪种类型的格子
    • 终点特殊处理:终点 \(B\) 可以接受任意类型的上一步
  3. 关键步骤

    • 初始化:读取 \(n\)(网格大小)和网格,记录起点 \(A\) 和终点 \(B\) 的坐标
    • 初始化队列:从起点 \(A\) 出发,向四个方向扩展,根据邻居格子类型(+-)初始化 dist 数组并入队
    • BFS扩展
      • 对于每个状态,计算下一步需要的格子类型 need = 1 - last
      • 只有当邻居格子类型等于 need(或为终点 \(B\))时才能转移
      • 更新新状态的 nlast 值并入队
    • 答案提取:取终点在两种状态下的最小距离
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^2 \times 2)\),每个格子最多访问两次(两种状态)
    • 空间复杂度:\(O(n^2 \times 2)\),需要存储三维距离数组
  5. 状态扩展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
posted @ 2026-06-14 12:00  团爸讲算法  阅读(7)  评论(0)    收藏  举报