I’m stuck!
把方向受限地图建成有向图,分别从 S 正向搜索和从 T 在反图搜索,再统计可达集合差集。
OJ: shumeng
题目 ID: CSP201312E
难度:普及+/提高-
标签:图BFS反图可达性网格
日期: 2026-07-31 16:21
形式化题目
给定 S 出发可达的格子集合。若 S 无法到达 T,输出 I'm stuck!;否则统计 T 的格子数。
思路
先看直接按照定义判断的朴素做法:
/**
* 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。若有
反图搜索
令 S 在原图可达的格子集合。题目第二个性质需要判断某格能否到达 T,这正是反图搜索的用途:把每条边反向后,从 T 搜索得到的集合 T 的格子。
因此先做原图 BFS 得到 from_start。若 T 不在其中,按题意输出 I'm stuck!。否则建反图并从 T BFS 得到 to_target,答案就是:
也就是遍历所有格子,统计 from_start[r][c] && !to_target[r][c]。S、T 与 + 都有四个方向的出边;- 只允许左右,| 只允许上下,. 只允许向下。
样例双向可达性
下表把官方样例的格子按可达性分类:B 表示既能从 S 到达又能到达 T,X 表示只能从 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 都在集合 R 格虽然可到达 T,但从 S 不能进入,不满足第一个性质。
这也说明正向与反向两次搜索不能互相替代:它们分别回答题目的两个方向问题。
代码
/**
* 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 与统计共需
总结
遇到单向移动规则时,先把网格看成有向图。要同时判断“从起点能到”和“能到终点”,分别做一次正向搜索和一次从终点出发的反图搜索,再取集合差即可。
图示解析
这张图串起本题从地图规则到统计答案的主线:
每个格子按自身字符决定有向边
|- 从 S 正向搜索,得到集合 A:S 能到达的格子
`- 建立反图后从 T 搜索,得到集合 B:能到达 T 的格子
|- T 不在 A 中:输出 I'm stuck!
`- 否则统计 A 中不属于 B 的格子反图中的搜索方向翻转了原图每一条边,因此从 T 反向可达恰好等价于原图中可以到达 T。
两个集合分别对应题目的两个性质,取差集不需要从每个格子重复搜索。
每个格子和每条可能移动边只处理常数次。