I’m stuck!

把方向受限地图建成有向图,分别从 S 正向搜索和从 T 在反图搜索,再统计可达集合差集。

OJ: shumeng

题目 ID: CSP201312E

难度:普及+/提高-

标签:BFS反图可达性网格

日期: 2026-07-31 16:21

形式化题目

给定 R×CR\times C 的网格,每个非障碍格子按自身字符决定下一步能走的四个方向。设 AA 为从起点 S 出发可达的格子集合。若 S 无法到达 T,输出 I'm stuck!;否则统计 AA 中不能到达目标 T 的格子数。

思路

先看直接按照定义判断的朴素做法:

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:49
 */
// brute.cpp:小数据暴力解,对每个从 S 可达的格子单独搜索 T。
#include <bits/stdc++.h>
using namespace std;

const int MAXR = 55;
const int MAXC = 55;

int R, C;
char grid[MAXR][MAXC];
bool from_start[MAXR][MAXC], vis[MAXR][MAXC];
int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};

bool inside(int r, int c) {
    return r >= 1 && r <= R && c >= 1 && c <= C;
}

bool allow_direction(char cell, int direction) {
    if (cell == '-' && direction < 2) {
        return false;
    }
    if (cell == '|' && direction >= 2) {
        return false;
    }
    if (cell == '.' && direction != 1) {
        return false;
    }
    return true;
}

void bfs_mark_start(int start_r, int start_c) {
    queue<pair<int, int> > q;
    from_start[start_r][start_c] = true;
    q.push(make_pair(start_r, start_c));

    while (!q.empty()) {
        pair<int, int> now = q.front();
        q.pop();

        int r = now.first;
        int c = now.second;
        for (int direction = 0; direction < 4; direction++) {
            if (!allow_direction(grid[r][c], direction)) {
                continue;
            }

            int nr = r + dr[direction];
            int nc = c + dc[direction];
            if (!inside(nr, nc) || grid[nr][nc] == '#' || from_start[nr][nc]) {
                continue;
            }

            from_start[nr][nc] = true;
            q.push(make_pair(nr, nc));
        }
    }
}

bool can_reach_target(int start_r, int start_c, int target_r, int target_c) {
    memset(vis, 0, sizeof(vis));
    queue<pair<int, int> > q;
    vis[start_r][start_c] = true;
    q.push(make_pair(start_r, start_c));

    while (!q.empty()) {
        pair<int, int> now = q.front();
        q.pop();

        int r = now.first;
        int c = now.second;
        if (r == target_r && c == target_c) {
            return true;
        }

        for (int direction = 0; direction < 4; direction++) {
            if (!allow_direction(grid[r][c], direction)) {
                continue;
            }

            int nr = r + dr[direction];
            int nc = c + dc[direction];
            if (!inside(nr, nc) || grid[nr][nc] == '#' || vis[nr][nc]) {
                continue;
            }

            vis[nr][nc] = true;
            q.push(make_pair(nr, nc));
        }
    }

    return false;
}

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

    cin >> R >> C;
    int start_r = 0, start_c = 0, target_r = 0, target_c = 0;
    for (int r = 1; r <= R; r++) {
        for (int c = 1; c <= C; c++) {
            cin >> grid[r][c];
            if (grid[r][c] == 'S') {
                start_r = r;
                start_c = c;
            }
            if (grid[r][c] == 'T') {
                target_r = r;
                target_c = c;
            }
        }
    }

    bfs_mark_start(start_r, start_c);
    if (!from_start[target_r][target_c]) {
        cout << "I'm stuck!\n";
        return 0;
    }

    int answer = 0;
    for (int r = 1; r <= R; r++) {
        for (int c = 1; c <= C; c++) {
            if (from_start[r][c] && !can_reach_target(r, c, target_r, target_c)) {
                answer++;
            }
        }
    }

    cout << answer << '\n';
    return 0;
}

它先从 S 搜索所有可达格子,再对每个这样的格子单独搜索 T。若有 VV 个格子、EE 条合法移动边,最坏会重复做 VV 次图搜索,复杂度为 O(V(V+E))O(V(V+E))

反图搜索

AA 为从 S 在原图可达的格子集合。题目第二个性质需要判断某格能否到达 T,这正是反图搜索的用途:把每条边反向后,从 T 搜索得到的集合 BB,恰好是原图中可以到达 T 的格子。

因此先做原图 BFS 得到 from_start。若 T 不在其中,按题意输出 I'm stuck!。否则建反图并从 T BFS 得到 to_target,答案就是:

AB |A\setminus B|

也就是遍历所有格子,统计 from_start[r][c] && !to_target[r][c]ST+ 都有四个方向的出边;- 只允许左右,| 只允许上下,. 只允许向下。

样例双向可达性

下表把官方样例的格子按可达性分类:B 表示既能从 S 到达又能到达 TX 表示只能从 S 到达,R 表示只能到达 T# 是障碍。

行/列 1 2 3 4 5
1 B B B B B
2 R R B # X
3 B R B # #
4 B B B B B
5 # # # # X

两个 X 都在集合 AA 中却不在集合 BB 中,所以样例答案为 2。R 格虽然可到达 T,但从 S 不能进入,不满足第一个性质。 这也说明正向与反向两次搜索不能互相替代:它们分别回答题目的两个方向问题。

代码

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:49
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXR = 55;
const int MAXC = 55;

int R, C;
char grid[MAXR][MAXC];
bool from_start[MAXR][MAXC], to_target[MAXR][MAXC];
vector<pair<int, int> > reverse_edge[MAXR][MAXC];

int dr[4] = {-1, 1, 0, 0};
int dc[4] = {0, 0, -1, 1};

bool inside(int r, int c) {
    return r >= 1 && r <= R && c >= 1 && c <= C;
}

bool allow_direction(char cell, int direction) {
    if (cell == '-' && direction < 2) {
        return false;
    }
    if (cell == '|' && direction >= 2) {
        return false;
    }
    if (cell == '.' && direction != 1) {
        return false;
    }
    return true;
}

void bfs_from_start(int start_r, int start_c) {
    queue<pair<int, int> > q;
    from_start[start_r][start_c] = true;
    q.push(make_pair(start_r, start_c));

    while (!q.empty()) {
        pair<int, int> now = q.front();
        q.pop();

        int r = now.first;
        int c = now.second;
        for (int direction = 0; direction < 4; direction++) {
            if (!allow_direction(grid[r][c], direction)) {
                continue;
            }

            int nr = r + dr[direction];
            int nc = c + dc[direction];
            if (!inside(nr, nc) || grid[nr][nc] == '#' || from_start[nr][nc]) {
                continue;
            }

            from_start[nr][nc] = true;
            q.push(make_pair(nr, nc));
        }
    }
}

void bfs_to_target(int target_r, int target_c) {
    queue<pair<int, int> > q;
    to_target[target_r][target_c] = true;
    q.push(make_pair(target_r, target_c));

    while (!q.empty()) {
        pair<int, int> now = q.front();
        q.pop();

        int r = now.first;
        int c = now.second;
        for (int i = 0; i < (int)reverse_edge[r][c].size(); i++) {
            int pre_r = reverse_edge[r][c][i].first;
            int pre_c = reverse_edge[r][c][i].second;
            if (to_target[pre_r][pre_c]) {
                continue;
            }

            to_target[pre_r][pre_c] = true;
            q.push(make_pair(pre_r, pre_c));
        }
    }
}

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

    cin >> R >> C;
    int start_r = 0, start_c = 0, target_r = 0, target_c = 0;

    for (int r = 1; r <= R; r++) {
        for (int c = 1; c <= C; c++) {
            cin >> grid[r][c];
            if (grid[r][c] == 'S') {
                start_r = r;
                start_c = c;
            }
            if (grid[r][c] == 'T') {
                target_r = r;
                target_c = c;
            }
        }
    }

    // 建反图:原图中 (r, c) 能到 (nr, nc),就记录其反向前驱。
    for (int r = 1; r <= R; r++) {
        for (int c = 1; c <= C; c++) {
            if (grid[r][c] == '#') {
                continue;
            }
            for (int direction = 0; direction < 4; direction++) {
                if (!allow_direction(grid[r][c], direction)) {
                    continue;
                }

                int nr = r + dr[direction];
                int nc = c + dc[direction];
                if (!inside(nr, nc) || grid[nr][nc] == '#') {
                    continue;
                }
                reverse_edge[nr][nc].push_back(make_pair(r, c));
            }
        }
    }

    bfs_from_start(start_r, start_c);
    if (!from_start[target_r][target_c]) {
        cout << "I'm stuck!\n";
        return 0;
    }

    bfs_to_target(target_r, target_c);
    int answer = 0;
    for (int r = 1; r <= R; r++) {
        for (int c = 1; c <= C; c++) {
            if (from_start[r][c] && !to_target[r][c]) {
                answer++;
            }
        }
    }

    cout << answer << '\n';
    return 0;
}

复杂度

每个格子最多有 4 条边,建图、两次 BFS 与统计共需 O(V+E)=O(RC)O(V+E)=O(RC) 时间,反图和访问数组使用 O(V+E)O(V+E) 空间。

总结

遇到单向移动规则时,先把网格看成有向图。要同时判断“从起点能到”和“能到终点”,分别做一次正向搜索和一次从终点出发的反图搜索,再取集合差即可。

图示解析

这张图串起本题从地图规则到统计答案的主线:

text
每个格子按自身字符决定有向边
|- 从 S 正向搜索,得到集合 A:S 能到达的格子
`- 建立反图后从 T 搜索,得到集合 B:能到达 T 的格子
   |- T 不在 A 中:输出 I'm stuck!
   `- 否则统计 A 中不属于 B 的格子

反图中的搜索方向翻转了原图每一条边,因此从 T 反向可达恰好等价于原图中可以到达 T。 两个集合分别对应题目的两个性质,取差集不需要从每个格子重复搜索。 每个格子和每条可能移动边只处理常数次。