[CERC1996] 机器人搬重物

GitHub跳转原题关系图返回列表

把机器人所在格点和朝向一起作为状态做 BFS,并预处理中心能否站在某个格点。

OJ: luogu

题目 ID: P1126

难度:普及+/提高

标签:bfs最短路图论网格模拟

日期: 2026-06-20 14:36

题意

给一个 N * M 的障碍网格。

机器人中心站在格点上,初始有一个朝向。每秒可以执行一条指令:

  • 左转
  • 右转
  • 前进 1 步
  • 前进 2 步
  • 前进 3 步

要求求出从起点到终点的最少时间。如果无法到达,输出 -1

思路

先看一个更直接的写法:

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

const int MAXN = 55;
const int INF = 0x3f3f3f3f;

int n, m;
int obs[MAXN][MAXN];
int dist_arr[MAXN][MAXN][4];

int sx, sy, tx, ty;
string dir_name;

int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};

struct Node {
    int x, y, dir;
};

int get_dir_id(const string &s) {
    if (s == "E") return 0;
    if (s == "S") return 1;
    if (s == "W") return 2;
    return 3;
}

bool can_stand_here(int x, int y) {
    if (x < 1 || x >= n || y < 1 || y >= m) {
        return false;
    }

    // 直接现场检查四个相邻方格是否有障碍。
    if (obs[x][y] || obs[x - 1][y] || obs[x][y - 1] || obs[x - 1][y - 1]) {
        return false;
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> obs[i][j];
        }
    }
    cin >> sx >> sy >> tx >> ty >> dir_name;

    if (!can_stand_here(sx, sy) || !can_stand_here(tx, ty)) {
        cout << -1 << '\n';
        return 0;
    }

    memset(dist_arr, 0x3f, sizeof(dist_arr));
    queue<Node> q;

    int start_dir = get_dir_id(dir_name);
    dist_arr[sx][sy][start_dir] = 0;
    q.push({sx, sy, start_dir});

    while (!q.empty()) {
        Node cur = q.front();
        q.pop();

        int d = dist_arr[cur.x][cur.y][cur.dir];
        if (cur.x == tx && cur.y == ty) {
            cout << d << '\n';
            return 0;
        }

        int left_dir = (cur.dir + 3) % 4;
        if (dist_arr[cur.x][cur.y][left_dir] > d + 1) {
            dist_arr[cur.x][cur.y][left_dir] = d + 1;
            q.push({cur.x, cur.y, left_dir});
        }

        int right_dir = (cur.dir + 1) % 4;
        if (dist_arr[cur.x][cur.y][right_dir] > d + 1) {
            dist_arr[cur.x][cur.y][right_dir] = d + 1;
            q.push({cur.x, cur.y, right_dir});
        }

        for (int step = 1; step <= 3; step++) {
            int nx = cur.x + dx[cur.dir] * step;
            int ny = cur.y + dy[cur.dir] * step;
            if (!can_stand_here(nx, ny)) {
                break;
            }
            if (dist_arr[nx][ny][cur.dir] > d + 1) {
                dist_arr[nx][ny][cur.dir] = d + 1;
                q.push({nx, ny, cur.dir});
            }
        }
    }

    cout << -1 << '\n';
    return 0;
}

本题的核心有两个。

第一,状态不能只看坐标,还必须把朝向带上。

因为同一个位置,如果面朝不同方向,下一条“前进”指令到达的位置完全不同。

所以状态要写成:

text
(x, y, dir)

第二,要先弄清楚“哪里不能站”。

题目给的是障碍 格子,但机器人中心站在 格点 上,而且机器人直径大于 1。

因此某个格点只要周围四个小方格里有任意一个障碍,这个格点就不能站。

接下来就很自然了:

  1. 预处理每个格点是否可站
  2. (x, y, dir) 作为状态做 BFS
  3. 每次扩展 5 种操作:
    • 左转
    • 右转
    • 前进 1 步
    • 前进 2 步
    • 前进 3 步

因为每条指令代价都相同,BFS 第一次到达目标位置时就是最优答案。

代码

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

const int MAXN = 55;
const int INF = 0x3f3f3f3f;

int n, m;
int obs[MAXN][MAXN];          // 原始障碍格,按题目给出的 N*M 网格存
int can_stand[MAXN][MAXN];    // 机器人中心能否站在这个格点
int dist_arr[MAXN][MAXN][4];  // 最短路:位置 + 朝向

int sx, sy, tx, ty;
string dir_name;

// 方向顺序:东、南、西、北
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};

struct Node {
    int x, y, dir;
};

int get_dir_id(const string &s) {
    if (s == "E") return 0;
    if (s == "S") return 1;
    if (s == "W") return 2;
    return 3; // N
}

bool inside_point(int x, int y) {
    // 机器人中心只能站在内部格点,边界上不行。
    return x >= 1 && x < n && y >= 1 && y < m;
}

void build_can_stand() {
    for (int x = 1; x < n; x++) {
        for (int y = 1; y < m; y++) {
            // 机器人直径大于 1,所以中心站在格点 (x,y) 时,
            // 周围四个小方格只要有一个是障碍,这个点就不能站。
            if (obs[x][y] || obs[x - 1][y] || obs[x][y - 1] || obs[x - 1][y - 1]) {
                can_stand[x][y] = 0;
            }
            else {
                can_stand[x][y] = 1;
            }
        }
    }
}

int bfs(int start_dir) {
    memset(dist_arr, 0x3f, sizeof(dist_arr));

    if (!inside_point(sx, sy) || !inside_point(tx, ty)) {
        return -1;
    }
    if (!can_stand[sx][sy] || !can_stand[tx][ty]) {
        return -1;
    }

    queue<Node> q;
    dist_arr[sx][sy][start_dir] = 0;
    q.push({sx, sy, start_dir});

    while (!q.empty()) {
        Node cur = q.front();
        q.pop();

        int now_dist = dist_arr[cur.x][cur.y][cur.dir];
        if (cur.x == tx && cur.y == ty) {
            return now_dist;
        }

        // 左转
        int left_dir = (cur.dir + 3) % 4;
        if (dist_arr[cur.x][cur.y][left_dir] > now_dist + 1) {
            dist_arr[cur.x][cur.y][left_dir] = now_dist + 1;
            q.push({cur.x, cur.y, left_dir});
        }

        // 右转
        int right_dir = (cur.dir + 1) % 4;
        if (dist_arr[cur.x][cur.y][right_dir] > now_dist + 1) {
            dist_arr[cur.x][cur.y][right_dir] = now_dist + 1;
            q.push({cur.x, cur.y, right_dir});
        }

        // 向前走 1/2/3 步,每种指令代价都是 1。
        for (int step = 1; step <= 3; step++) {
            int nx = cur.x + dx[cur.dir] * step;
            int ny = cur.y + dy[cur.dir] * step;

            // 只要中途某一步撞到边界或障碍,后面更远的步数也都不能走了。
            if (!inside_point(nx, ny) || !can_stand[nx][ny]) {
                break;
            }

            if (dist_arr[nx][ny][cur.dir] > now_dist + 1) {
                dist_arr[nx][ny][cur.dir] = now_dist + 1;
                q.push({nx, ny, cur.dir});
            }
        }
    }

    return -1;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> obs[i][j];
        }
    }
    cin >> sx >> sy >> tx >> ty >> dir_name;

    build_can_stand();
    cout << bfs(get_dir_id(dir_name)) << '\n';

    return 0;
}

复杂度

  • 时间复杂度:O(NM)O(N * M)
  • 空间复杂度:O(NM)O(N * M)

总结

这题是很典型的“状态不只包含位置,还要包含方向”的 BFS。

真正容易写错的地方只有两个:

  1. 障碍判定针对的是格点,不是原始格子
  2. 前进 2/3 步时,中间经过的位置也必须全部合法