游戏

危险结束前在时间扩展网格 BFS,时刻 101 后转为多源普通 BFS。

OJ: shumeng

题目 ID: CSP201604D

难度:普及-

标签:BFS最短路网格图

日期: 2026-07-31 16:21

形式化题目

n×mn \times m 的网格中,从 (1,1)(1,1) 出发,每个时刻必须向相邻格移动一格,目标是到达 (n,m)(n,m)。若干格子在连续时间区间 [a,b][a,b] 内危险,到达危险格则失败。求最早到达终点的时间。

思路

危险是随时间变化的,直接把“时刻”作为状态的一部分即可处理。

时间扩展 BFS

状态 (r,c,t)(r, c, t) 表示恰好在时刻 tt 到达格子 (r,c)(r,c)。从起点 (1,1,0)(1,1,0) 出发 BFS,扩展时检查目标格在下一时刻是否危险:若时刻落在它的危险区间 [a,b][a,b] 内就不可进入。

题目保证所有危险区间满足 b100b \le 100,因此时刻 101 之后所有格子都安全。时间维度只需记录到时刻 101。

后半段普通 BFS

到达时刻 101 后网格不再变化,把时刻 101 所有可达格子作为多源点,在静态网格上做一次普通 BFS,就能求出每个点到达终点的最短剩余步数。

最终答案是最早到达时刻,两部分拼在一起即为最小总时间。

正确性

BFS 按时间递增展开,第一次弹出终点时一定是最早到达时刻;时刻 101 后的多源 BFS 保证每个源点都从最小时间继续扩展,不会漏掉更优路线。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:48
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 105;
const int MAX_TIME = 101; // 所有危险在时刻 100 内结束,时刻 101 起所有格子都是安全的

int n, m;
int danger_start[MAXN][MAXN];               // 方格开始危险的时刻,-1 表示从不危险
int danger_end[MAXN][MAXN];                 // 方格危险结束的时刻
int visited[MAXN][MAXN][MAX_TIME + 1];      // visited[r][c][t] 是否恰在时刻 t 到达该格
int distance_to[MAXN][MAXN];                // 时刻 MAX_TIME 之后的普通最短路
int dr[4] = {1, -1, 0, 0};
int dc[4] = {0, 0, 1, -1};

// BFS 状态:行、列、到达时刻
struct State {
    int row, column, time;
};

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

    int t;
    cin >> n >> m >> t;
    memset(danger_start, -1, sizeof(danger_start));
    memset(danger_end, -1, sizeof(danger_end));
    for (int i = 1; i <= t; i++) {
        int r, c, a, b;
        cin >> r >> c >> a >> b;
        danger_start[r - 1][c - 1] = a;
        danger_end[r - 1][c - 1] = b;
    }

    // 第一步:带上时间维度的 BFS,把危险时段内的障碍按时刻区分开。
    queue<State> q;
    q.push({0, 0, 0});
    visited[0][0][0] = 1;
    while (!q.empty()) {
        State current = q.front();
        q.pop();
        if (current.row == n - 1 && current.column == m - 1) {
            cout << current.time << '\n';
            return 0;
        }
        if (current.time == MAX_TIME) {
            continue; // 时间维度只记录到 MAX_TIME,之后交给普通 BFS
        }
        int next_time = current.time + 1;
        for (int i = 0; i < 4; i++) {
            int nr = current.row + dr[i];
            int nc = current.column + dc[i];
            if (nr < 0 || nr >= n || nc < 0 || nc >= m) {
                continue;
            }
            if (visited[nr][nc][next_time]) {
                continue;
            }
            // 到达目标格的时刻不能落在它的危险区间内
            if (danger_start[nr][nc] <= next_time && next_time <= danger_end[nr][nc]) {
                continue;
            }
            visited[nr][nc][next_time] = 1;
            q.push({nr, nc, next_time});
        }
    }

    // 第二步:时刻 MAX_TIME 之后网格不再变化,把此时所有可达格作为多源点
    // 做普通 BFS,求出它们到终点的最短剩余步数。
    queue<pair<int, int> > safe_queue;
    memset(distance_to, -1, sizeof(distance_to));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (visited[i][j][MAX_TIME]) {
                safe_queue.push(make_pair(i, j));
                distance_to[i][j] = MAX_TIME;
            }
        }
    }
    while (!safe_queue.empty()) {
        pair<int, int> current = safe_queue.front();
        safe_queue.pop();
        for (int i = 0; i < 4; i++) {
            int nr = current.first + dr[i];
            int nc = current.second + dc[i];
            if (nr < 0 || nr >= n || nc < 0 || nc >= m) {
                continue;
            }
            if (distance_to[nr][nc] != -1) {
                continue;
            }
            distance_to[nr][nc] = distance_to[current.first][current.second] + 1;
            safe_queue.push(make_pair(nr, nc));
        }
    }

    cout << distance_to[n - 1][m - 1] << '\n';
    return 0;
}

复杂度

  • 时间:第一段 BFS 最多展开 101×n×m101 \times n \times m 个状态,第二段普通 BFS 访问每个格子常数次,时间复杂度为 O(101nm)O(101nm)
  • 空间:记录各时刻的可达状态需要 O(101nm)O(101nm)

总结

危险判断针对“到达时刻”,而不是离开时刻。当所有危险时段结束后,时间维度可以安全消去,把剩余问题退化为普通最短路。