欢迎光临
我们一直在努力

UVa 10231 Matrix World

题目描述

在现实世界中,有一个 M×NM \\times NM×N 的网格地图(2≤N,M≤302 \\leq N, M \\leq 302N,M30)。地图中包含以下几种元素:

  • * :钻石宝藏
  • . :空单元格
  • # :岩石(不可通过)
  • X :警卫机器人
  • O :你的起始位置

你从一个单元格移动到相邻单元格需要 111 秒,收集一个宝藏也需要 111 秒。警卫机器人也以 111 格/秒的速度移动。你不能走出地图边界,警卫也无法进入地图(但警卫可以在地图内移动)。

你需要收集尽可能多的宝藏。如果收集到相同数量的宝藏有多种方案,选择总时间最小的方案。注意:如果你到达一个宝藏单元格的时刻为 ttt,那么你将在 t+1t+1t+1 秒完成收集。如果此时有警卫也到达该单元格(即警卫到达时间 ≤t+1\\leq t+1t+1),则该宝藏不能计入收集结果,因为你没有时间逃脱。

输入包含多个测试用例,输出格式如下:

  • 每个用例先输出 Case t:
  • 如果无法收集任何宝藏,输出 No treasures can be collected.
  • 否则输出:
    • Maximum number of collectible treasures: max.
    • Minimum Time: min sec.
  • 每个用例后输出一个空行。

题目分析

本题是一个 带有时间约束的路径规划 + 状态压缩动态规划 问题。地图规模最大为 30×3030 \\times 3030×30,但宝藏数量不超过 101010,这提示我们可以对宝藏集合进行状态压缩。

核心难点

  • 警卫的动态影响:警卫从各自的位置同时出发,其可达范围随时间扩张。我们需要知道每个单元格被警卫最早到达的时间,从而判断玩家能否安全经过或停留。
  • 收集时间与逃脱时间:玩家在宝藏格必须额外停留 111 秒来完成收集,这比普通移动更危险,因为警卫可能在这 111 秒内赶到。
  • 最优顺序:由于宝藏数量少,我们可以枚举收集顺序,但直接枚举 10!10!10! 不可行,因此采用状态压缩 DP\\texttt{DP}DP
  • 关键观察

    • 玩家的安全条件:若玩家在时刻 ttt 到达单元格 (i,j)(i, j)(i,j),则必须满足 t<guardTime[i][j]t < guardTime[i][j]t<guardTime[i][j]。对于收集操作,若在时刻 ttt 开始收集,则收集完成时刻为 t+1t+1t+1,必须满足 t+1<guardTime[i][j]t+1 < guardTime[i][j]t+1<guardTime[i][j]
    • 警卫的移动不受玩家影响,且所有警卫同时从初始位置出发。这允许我们预先用 BFS\\texttt{BFS}BFS 计算出每个格子的警卫最早到达时间。
    • 玩家只能走空单元格、宝藏格、起始格,不能走岩石格。
    • 由于地图大小有限,任意两点之间的 安全最短路径 可以预处理(使用 BFS\\texttt{BFS}BFS,排除不安全的时刻)。

    解题思路

    第一步:预处理警卫到达时间

    guardTime[x][y]guardTime[x][y]guardTime[x][y] 表示任意警卫最早到达单元格 (x,y)(x, y)(x,y) 的时间。由于所有警卫同时出发,我们可以将所有警卫的初始位置加入队列,运行多源 BFS\\texttt{BFS}BFS。对于岩石格 #,警卫也无法进入,因此 BFS\\texttt{BFS}BFS 中跳过它们。

    初始化:每个警卫位置的 guardTime=0guardTime = 0guardTime=0
    转移:从 (x,y)(x, y)(x,y) 走到相邻的 (nx,ny)(nx, ny)(nx,ny),若 guardTime[nx][ny]>guardTime[x][y]+1guardTime[nx][ny] > guardTime[x][y] + 1guardTime[nx][ny]>guardTime[x][y]+1,则更新并入队。

    第二步:计算玩家安全移动的时间矩阵

    设起点为 SSS,宝藏共有 KKK 个(K≤10K \\leq 10K10)。我们构建一个 (K+1)×(K+1)(K+1) \\times (K+1)(K+1)×(K+1) 的距离矩阵 distdistdist,其中索引 000 代表起点,索引 1…K1 \\ldots K1K 代表各个宝藏。

    对于任意两个点 AAABBB(可以是起点或宝藏),我们使用 BFS\\texttt{BFS}BFS 计算从 AAABBB 的 安全最短路径长度(即移动步数)。BFS\\texttt{BFS}BFS 的限制条件:

    • 只能走 '.'、'*'、'O'(起点位置)。
    • 当从当前单元格到达邻居单元格时,设到达邻居的时刻为 t+1t+1t+1,必须满足 t+1<guardTime[neighbor]t+1 < guardTime[neighbor]t+1<guardTime[neighbor]

    注意:如果起点本身在时刻 000 就与警卫重合(即起点有警卫),则无法开始任何行动。在我们的多源 BFS\\texttt{BFS}BFS 中,若 guardTime[S]=0guardTime[S] = 0guardTime[S]=0,则玩家无法存活。

    第三步:状态压缩 DP\\texttt{DP}DP

    我们使用二进制掩码表示已经收集的宝藏集合。定义:

    dp[mask][i]=收集完掩码 mask 中的宝藏,且最后收集的宝藏是 i 的最小总时间(已完成收集)
    dp[mask][i] = \\text{收集完掩码 } mask \\text{ 中的宝藏,且最后收集的宝藏是 } i \\text{ 的最小总时间(已完成收集)}
    dp[mask][i]=收集完掩码 mask 中的宝藏,且最后收集的宝藏是 i 的最小总时间(已完成收集)

    其中 iii 是宝藏的编号(0≤i<K0 \\leq i < K0i<K)。注意 dp[mask][i]dp[mask][i]dp[mask][i] 已经包含了最后一次收集所需的 111 秒。

    初始化:对于每个宝藏 iii,计算从起点 SSS 走到宝藏 iii 的安全时间 tmovet_{move}tmove。若 tmove<∞t_{move} < \\inftytmove<tmove+1<guardTime[宝藏i]t_{move} + 1 < guardTime[宝藏_i]tmove+1<guardTime[i],则:

    dp[1≪i][i]=tmove+1
    dp[1 \\ll i][i] = t_{move} + 1
    dp[1i][i]=tmove+1

    状态转移:对于当前状态 (mask,last)(mask, last)(mask,last),尝试前往下一个未收集的宝藏 nxtnxtnxt。设从 lastlastlastnxtnxtnxt 的安全移动时间为 d=dist[last+1][nxt+1]d = dist[last+1][nxt+1]d=dist[last+1][nxt+1](注意索引偏移)。设到达 nxtnxtnxt 的时刻为 arrive=dp[mask][last]+darrive = dp[mask][last] + darrive=dp[mask][last]+d,完成收集的时刻为 collectEnd=arrive+1collectEnd = arrive + 1collectEnd=arrive+1。若 collectEnd<guardTime[宝藏nxt]collectEnd < guardTime[宝藏_{nxt}]collectEnd<guardTime[nxt],则更新:

    dp[mask∣(1≪nxt)][nxt]=min⁡(dp[mask∣(1≪nxt)][nxt], collectEnd)
    dp[mask \\mid (1 \\ll nxt)][nxt] = \\min(dp[mask \\mid (1 \\ll nxt)][nxt],\\ collectEnd)
    dp[mask(1nxt)][nxt]=min(dp[mask(1nxt)][nxt], collectEnd)

    最终答案:遍历所有 maskmaskmasklastlastlast,找出 popcount(mask)popcount(mask)popcount(mask) 最大的 dp[mask][last]dp[mask][last]dp[mask][last],若存在多个相同数量的宝藏,取时间最小的。

    第四步:边界情况处理

    • 如果起点就是宝藏?题目中没有说明,但按逻辑起点是 O,宝藏是 *,二者不同。
    • 如果地图中没有宝藏,直接输出无法收集。
    • 如果所有宝藏都被警卫包围导致无法安全收集,也输出无法收集。

    复杂度分析

    • 多源 BFS\\texttt{BFS}BFSO(M×N)O(M \\times N)O(M×N)
    • 预处理每对起点/宝藏之间的安全距离:O((K+1)2×M×N)O((K+1)^2 \\times M \\times N)O((K+1)2×M×N),由于 K≤10K \\leq 10K10M,N≤30M, N \\leq 30M,N30,最多约 112×900=10890011^2 \\times 900 = 108900112×900=108900BFS\\texttt{BFS}BFS,每次 BFS\\texttt{BFS}BFSO(MN)O(MN)O(MN),实际可接受。
    • DP 状态数:2K×K≈1024×10=102402^K \\times K \\approx 1024 \\times 10 = 102402K×K1024×10=10240,转移 O(K)O(K)O(K),总 O(2K×K2)O(2^K \\times K^2)O(2K×K2),非常快。

    代码实现

    // Matrix World
    // UVa ID: 10231
    // Verdict: Accepted
    // Submission Date: 2026-05-29
    // UVa Run Time: 0.010s
    //
    // 版权所有(C)2026,邱秋。metaphysis # yeah dot net

    #include <bits/stdc++.h>
    using namespace std;

    const int INF = 1e9;
    const int dx[] = {0, 0, 1, 1};
    const int dy[] = {1, 1, 0, 0};

    struct State {
    int x, y, t;
    };

    int main() {
    int m, n, caseNo = 1;
    while (cin >> m >> n) {
    vector<string> grid(m);
    for (int i = 0; i < m; i++) cin >> grid[i];

    vector<pair<int, int>> treasures, guards;
    int sx = 1, sy = 1;

    // 收集位置信息:'*' 宝藏,'X' 警卫,'O' 起点
    for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
    if (grid[i][j] == '*') treasures.push_back({i, j});
    else if (grid[i][j] == 'X') guards.push_back({i, j});
    else if (grid[i][j] == 'O') sx = i, sy = j;
    }
    }

    int tCnt = treasures.size();

    // 多源BFS计算警卫到达每个格子的最短时间
    vector<vector<int>> guardTime(m, vector<int>(n, INF));
    queue<State> q;
    for (auto& g : guards) {
    guardTime[g.first][g.second] = 0;
    q.push({g.first, g.second, 0});
    }
    while (!q.empty()) {
    State cur = q.front(); q.pop();
    for (int d = 0; d < 4; d++) {
    int nx = cur.x + dx[d], ny = cur.y + dy[d];
    if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
    if (grid[nx][ny] == '#') continue;
    if (guardTime[nx][ny] > cur.t + 1) {
    guardTime[nx][ny] = cur.t + 1;
    q.push({nx, ny, cur.t + 1});
    }
    }
    }

    // BFS计算从(sx,sy)到(tx,ty)的安全最短路径时间(移动时间)
    auto bfs = [&](int sx, int sy, int tx, int ty) -> int {
    if (sx == tx && sy == ty) return 0;
    vector<vector<int>> dist(m, vector<int>(n, INF));
    queue<State> qq;
    // 起点必须在警卫到达之前才能开始(严格小于)
    if (guardTime[sx][sy] > 0) {
    dist[sx][sy] = 0;
    qq.push({sx, sy, 0});
    }
    while (!qq.empty()) {
    State cur = qq.front(); qq.pop();
    for (int d = 0; d < 4; d++) {
    int nx = cur.x + dx[d], ny = cur.y + dy[d];
    if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
    if (grid[nx][ny] == '#') continue;
    int nt = cur.t + 1;
    if (nt >= guardTime[nx][ny]) continue;
    if (dist[nx][ny] > nt) {
    dist[nx][ny] = nt;
    qq.push({nx, ny, nt});
    }
    }
    }
    return dist[tx][ty];
    };

    // 建立距离矩阵:索引0是起点,1..tCnt是宝藏
    vector<vector<int>> dist(tCnt + 1, vector<int>(tCnt + 1, INF));
    vector<pair<int, int>> points;
    points.push_back({sx, sy});
    for (auto& t : treasures) points.push_back(t);

    for (int i = 0; i <= tCnt; i++) {
    for (int j = i + 1; j <= tCnt; j++) {
    int d1 = bfs(points[i].first, points[i].second, points[j].first, points[j].second);
    int d2 = bfs(points[j].first, points[j].second, points[i].first, points[i].second);
    if (d1 < INF) dist[i][j] = d1;
    if (d2 < INF) dist[j][i] = d2;
    }
    }

    // 状态压缩DP:dp[mask][last] = 收集完last后总时间(已包含收集的1秒)
    vector<vector<int>> dp(1 << tCnt, vector<int>(tCnt, INF));
    for (int i = 0; i < tCnt; i++) {
    int d = bfs(sx, sy, points[i + 1].first, points[i + 1].second);
    if (d < INF) {
    int collectEnd = d + 1;
    if (collectEnd < guardTime[points[i + 1].first][points[i + 1].second])
    dp[1 << i][i] = collectEnd;
    }
    }

    for (int mask = 1; mask < (1 << tCnt); mask++) {
    for (int last = 0; last < tCnt; last++) {
    if (!(mask >> last & 1)) continue;
    if (dp[mask][last] == INF) continue;
    for (int nxt = 0; nxt < tCnt; nxt++) {
    if (mask >> nxt & 1) continue;
    int d = dist[last + 1][nxt + 1];
    if (d >= INF) continue;
    int arrive = dp[mask][last] + d;
    int collectEnd = arrive + 1;
    if (collectEnd < guardTime[points[nxt + 1].first][points[nxt + 1].second])
    dp[mask | (1 << nxt)][nxt] = min(dp[mask | (1 << nxt)][nxt], collectEnd);
    }
    }
    }

    // 找出最大可收集宝藏数及最小时间
    int maxTreasures = 0, minTime = INF;
    for (int mask = 1; mask < (1 << tCnt); mask++) {
    int cnt = __builtin_popcount(mask);
    if (cnt < maxTreasures) continue;
    for (int last = 0; last < tCnt; last++) {
    if (dp[mask][last] < INF) {
    if (cnt > maxTreasures) {
    maxTreasures = cnt;
    minTime = dp[mask][last];
    } else if (cnt == maxTreasures) {
    minTime = min(minTime, dp[mask][last]);
    }
    }
    }
    }

    cout << "Case " << caseNo++ << ":\\n";
    if (maxTreasures == 0) {
    cout << "No treasures can be collected.\\n";
    } else {
    cout << "Maximum number of collectible treasures: " << maxTreasures << ".\\n";
    cout << "Minimum Time: " << minTime << " sec.\\n";
    }
    cout << "\\n";
    }
    return 0;
    }

    总结

    本题的核心在于 将警卫的威胁转化为时间约束,然后通过预处理安全路径和状态压缩 DP\\texttt{DP}DP 来求解最优收集顺序。这种“时间约束 + 状态压缩”的模型在类似题目(如“收集物品并躲避敌人”)中非常常见,值得熟练掌握。

    赞(0)
    未经允许不得转载:171主机测评 » UVa 10231 Matrix World
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址