UVa 10818 Dora Trip

题目描述

大雄今天又没交作业,被老师惩罚后,妈妈还给他布置了一堆任务(用 * 表示),他需要去这些地方。但是路上可能会遇到老师或小夫(用 X 表示),他必须避开这些危险地点。大雄的家用 S 表示,他需要从家出发,访问尽可能多的任务地点,最后回到家中。地图由 rc 列的网格组成,其中:

  • 空格 ' ' 表示空地,可以通行。
  • '#' 表示墙,不能通行。
  • 'S' 是大雄的家,旅程从此处开始并在此处结束。
  • 'X' 是危险地点,不能进入。
  • '*' 是任务地点,最多有 101010 个。

地图的边界总是封闭的,即不能走出地图外。

要求输出一条最短路径,使得访问的任务点数量最多,如果有多条这样的最短路径,输出字典序最小的那条。路径用 NSEW 表示北、南、东、西(北指向上方)。如果无法访问任何任务点,则输出 Stay home!

输入格式
输入包含不超过 202020 个测试用例。每个用例第一行是两个整数 rrrccc1≤r,c≤201 \le r, c \le 201r,c20),接着 rrr 行每行 ccc 个字符表示地图。输入以 r=c=0r = c = 0r=c=0 结束。

输出格式
对于每个测试用例,按要求输出路径或 Stay home!

题目分析

问题本质

这是一个带状态约束的最短路径搜索问题:

  1. 状态空间:不仅要记录当前位置 (x,y)(x, y)(x,y),还要记录已经访问过的任务点集合(因为最多 101010 个任务点,可以用一个二进制掩码 mask 表示)。
  2. 目标:从 S 出发,最终回到 S,使得访问的任务点数量最多。在相同数量下,要求总路径最短,且字典序最小。
  3. 约束:不能进入 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),即从家出发,尚未访问任何任务点。
  • 扩展:每次从当前状态尝试向四个方向移动(按 ENSW 的顺序以保证字典序最小)。
  • 状态转移:如果新位置是任务点,则更新 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 时:

  1. 计算当前掩码中 111 的个数 cntcntcnt
  2. 与历史最优的个数 bestCntbestCntbestCnt 比较:
    • 如果 cnt>bestCntcnt > bestCntcnt>bestCnt,更新最优解。
    • 如果 cnt=bestCntcnt = bestCntcnt=bestCntdist<bestDistdist < bestDistdist<bestDist,也更新最优解(因为路径更短)。
  3. 由于 BFS\texttt{BFS}BFSENSW 顺序扩展,所以最早找到的最短路径自然就是字典序最小的,无需额外比较字典序。
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,c20210=10242^{10} = 1024210=1024,所以最多约 20×20×1024=40960020 \times 20 \times 1024 = 40960020×20×1024=409600 个状态。
  • BFS\texttt{BFS}BFS 每个状态扩展 444 个方向,总复杂度可接受。

注意事项

  1. 输入处理:地图行可能包含空格,需用 getline 读取整行,并确保每行长度为 ccc
  2. 起点处理BFS\texttt{BFS}BFS 初始状态距离为 000,但只有当距离大于 000 且回到 S 时才视为有效回路。
  3. 字典序保证:扩展方向顺序必须为 ENSW,这是英文字母的字典序,保证最先找到的路径字典序最小。
  4. 边界判断:移动时要检查是否越界以及是否为可通行单元格。

代码实现

// 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 题目,关键在于:

  1. 状态设计:将“已访问任务点集合”压缩进状态中。
  2. 搜索策略:使用 BFS\texttt{BFS}BFS 保证最短路径,按特定方向顺序保证字典序最小。
  3. 最优解更新:在回到起点时判断是否访问了更多任务点或路径更短。

通过合理的状态表示和 BFS\texttt{BFS}BFS 扩展,即可在给定的地图约束下找到满足条件的最优路径。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值