题目描述
大雄今天又没交作业,被老师惩罚后,妈妈还给他布置了一堆任务(用 * 表示),他需要去这些地方。但是路上可能会遇到老师或小夫(用 X 表示),他必须避开这些危险地点。大雄的家用 S 表示,他需要从家出发,访问尽可能多的任务地点,最后回到家中。地图由 r 行 c 列的网格组成,其中:
- 空格
' '表示空地,可以通行。 '#'表示墙,不能通行。'S'是大雄的家,旅程从此处开始并在此处结束。'X'是危险地点,不能进入。'*'是任务地点,最多有 101010 个。
地图的边界总是封闭的,即不能走出地图外。
要求输出一条最短路径,使得访问的任务点数量最多,如果有多条这样的最短路径,输出字典序最小的那条。路径用 N、S、E、W 表示北、南、东、西(北指向上方)。如果无法访问任何任务点,则输出 Stay home!。
输入格式
输入包含不超过 202020 个测试用例。每个用例第一行是两个整数 rrr 和 ccc(1≤r,c≤201 \le r, c \le 201≤r,c≤20),接着 rrr 行每行 ccc 个字符表示地图。输入以 r=c=0r = c = 0r=c=0 结束。
输出格式
对于每个测试用例,按要求输出路径或 Stay home!。
题目分析
问题本质
这是一个带状态约束的最短路径搜索问题:
- 状态空间:不仅要记录当前位置 (x,y)(x, y)(x,y),还要记录已经访问过的任务点集合(因为最多 101010 个任务点,可以用一个二进制掩码
mask表示)。 - 目标:从
S出发,最终回到S,使得访问的任务点数量最多。在相同数量下,要求总路径最短,且字典序最小。 - 约束:不能进入
X和#。
思路分析
1. 状态表示
每个状态用一个三元组 (x,y,mask)(x, y, mask)(x,y,mask) 表示:
- (x,y)(x, y)(x,y):当前位置坐标。
- maskmaskmask:一个 101010 位二进制数,第 iii 位为 111 表示第 iii 个任务点已被访问。
2. 搜索方法
使用 BFS\texttt{BFS}BFS(广度优先搜索) ,因为 BFS\texttt{BFS}BFS 按层扩展,可以保证第一次到达某个状态时的路径长度就是最短的。
搜索过程:
- 初始状态:(sx,sy,0)(sx, sy, 0)(sx,sy,0),即从家出发,尚未访问任何任务点。
- 扩展:每次从当前状态尝试向四个方向移动(按
E、N、S、W的顺序以保证字典序最小)。 - 状态转移:如果新位置是任务点,则更新
mask。 - 终止条件:当状态 (sx,sy,mask)(sx, sy, mask)(sx,sy,mask) 被访问且路径长度大于 000 时,说明找到了一条从家出发又回到家的回路。此时检查该回路访问的任务点数量。
3. 最优解判定
在 BFS\texttt{BFS}BFS 过程中,我们记录:
bestMask:当前找到的最多任务点掩码。bestDist:对应的最短路径长度。bestState:对应的最终状态。
当 BFS\texttt{BFS}BFS 到达一个家状态 (sx,sy,mask)(sx, sy, mask)(sx,sy,mask) 且路径长度 dist>0dist > 0dist>0 时:
- 计算当前掩码中 111 的个数 cntcntcnt。
- 与历史最优的个数 bestCntbestCntbestCnt 比较:
- 如果 cnt>bestCntcnt > bestCntcnt>bestCnt,更新最优解。
- 如果 cnt=bestCntcnt = bestCntcnt=bestCnt 但 dist<bestDistdist < bestDistdist<bestDist,也更新最优解(因为路径更短)。
- 由于 BFS\texttt{BFS}BFS 按
E、N、S、W顺序扩展,所以最早找到的最短路径自然就是字典序最小的,无需额外比较字典序。
4. 路径重建
为了输出路径,我们在 BFS\texttt{BFS}BFS 过程中记录每个状态的前驱状态以及从哪个方向移动而来。最后从最优解状态回溯到家,即可得到路径(注意要反转)。
复杂度分析
- 状态数:O(r×c×210)O(r \times c \times 2^{10})O(r×c×210),其中 r,c≤20r, c \le 20r,c≤20,210=10242^{10} = 1024210=1024,所以最多约 20×20×1024=40960020 \times 20 \times 1024 = 40960020×20×1024=409600 个状态。
- BFS\texttt{BFS}BFS 每个状态扩展 444 个方向,总复杂度可接受。
注意事项
- 输入处理:地图行可能包含空格,需用
getline读取整行,并确保每行长度为 ccc。 - 起点处理:BFS\texttt{BFS}BFS 初始状态距离为 000,但只有当距离大于 000 且回到
S时才视为有效回路。 - 字典序保证:扩展方向顺序必须为
E、N、S、W,这是英文字母的字典序,保证最先找到的路径字典序最小。 - 边界判断:移动时要检查是否越界以及是否为可通行单元格。
代码实现
// Dora Trip
// UVa ID: 10818
// Verdict: Accepted
// Submission Date: 2025-12-14
// UVa Run Time: 0.190s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 状态结构体:位置(x,y)和访问任务点的掩码mask
struct State {
int x, y, mask;
State(int x = 0, int y = 0, int mask = 0) : x(x), y(y), mask(mask) {}
// 用于map比较,需要定义小于运算符
bool operator<(const State& other) const {
if (x != other.x) return x < other.x;
if (y != other.y) return y < other.y;
return mask < other.mask;
}
};
int r, c; // 地图行数和列数
vector<string> grid; // 地图
int sx, sy; // 起点S的位置
int targetCount; // 任务点*的数量
vector<pair<int, int>> targets; // 任务点坐标
int targetIndex[20][20]; // 每个位置如果是*,记录其索引,否则为-1
// 四个方向:E, N, S, W(按字典序排列,保证最先找到的路径字典序最小)
const int dx[4] = {0, -1, 1, 0};
const int dy[4] = {1, 0, 0, -1};
const char dirChar[4] = {'E', 'N', 'S', 'W'};
map<State, pair<State, char>> pre; // 记录前驱状态和移动方向
map<State, int> dist; // 记录到达状态的最短距离
// 检查坐标(x,y)是否可通行
bool isValid(int x, int y) {
if (x < 0 || x >= r || y < 0 || y >= c) return false;
char ch = grid[x][y];
return ch != '#' && ch != 'X';
}
void solve() {
// 读取地图
grid.resize(r);
cin.ignore(); // 忽略第一行整数后的换行符
for (int i = 0; i < r; i++) {
getline(cin, grid[i]);
// 确保每行长度恰好为c,不足则用空格补齐
if ((int)grid[i].size() < c) grid[i].append(c - grid[i].size(), ' ');
}
// 初始化任务点信息
targets.clear();
memset(targetIndex, -1, sizeof(targetIndex));
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++) {
if (grid[i][j] == 'S') {
sx = i, sy = j;
} else if (grid[i][j] == '*') {
targetIndex[i][j] = targets.size();
targets.push_back({i, j});
}
}
targetCount = targets.size();
// 如果没有任务点,直接输出
if (targetCount == 0) {
cout << "Stay home!" << endl;
return;
}
// $\texttt{BFS}$初始化
pre.clear();
dist.clear();
queue<State> q;
State start(sx, sy, 0);
dist[start] = 0;
q.push(start);
int bestMask = 0; // 最优掩码(访问任务点最多)
int bestDist = 1e9; // 最优路径长度
State bestState = start; // 最优状态
bool found = false; // 是否找到可行解
while (!q.empty()) {
State cur = q.front(); q.pop();
int curDist = dist[cur];
// 如果当前状态在起点S且不是初始状态(dist>0),则是一条回路
if (cur.x == sx && cur.y == sy && curDist > 0) {
int cnt = __builtin_popcount(cur.mask); // 当前访问任务点数量
int bestCnt = __builtin_popcount(bestMask); // 历史最优数量
// 如果访问更多任务点,或数量相同但路径更短,则更新最优解
if (cnt > bestCnt || (cnt == bestCnt && curDist < bestDist)) {
bestMask = cur.mask;
bestDist = curDist;
bestState = cur;
found = true;
}
}
// 向四个方向扩展
for (int d = 0; d < 4; d++) {
int nx = cur.x + dx[d];
int ny = cur.y + dy[d];
if (!isValid(nx, ny)) continue;
int newMask = cur.mask;
// 如果新位置是任务点,更新掩码
if (targetIndex[nx][ny] != -1)
newMask |= (1 << targetIndex[nx][ny]);
State next(nx, ny, newMask);
// 如果该状态未访问过,入队
if (dist.find(next) == dist.end()) {
dist[next] = curDist + 1;
pre[next] = {cur, dirChar[d]}; // 记录前驱和方向
q.push(next);
}
}
}
// 没有找到可行回路
if (!found) {
cout << "Stay home!" << endl;
return;
}
// 从最优状态回溯,重建路径
string path = "";
State cur = bestState;
while (dist[cur] > 0) {
path += pre[cur].second; // 方向字符
cur = pre[cur].first; // 前驱状态
}
reverse(path.begin(), path.end()); // 回溯得到的是逆序,需要反转
cout << path << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
while (cin >> r >> c && (r || c)) solve();
return 0;
}
总结
本题是一道典型的状态压缩 BFS\texttt{BFS}BFS 题目,关键在于:
- 状态设计:将“已访问任务点集合”压缩进状态中。
- 搜索策略:使用 BFS\texttt{BFS}BFS 保证最短路径,按特定方向顺序保证字典序最小。
- 最优解更新:在回到起点时判断是否访问了更多任务点或路径更短。
通过合理的状态表示和 BFS\texttt{BFS}BFS 扩展,即可在给定的地图约束下找到满足条件的最优路径。
9572

被折叠的 条评论
为什么被折叠?



