UVa 1078 Steam Roller

题目描述

Johnny\texttt{Johnny}Johnny 驾驶一辆蒸汽压路机回家。蒸汽压路机速度慢,启动、改变方向和完全停止都需要较长时间。城市街道呈矩形网格状布局,每个交叉路口与最多四个相邻路口通过街道连接。每条街道的长度正好是一个街区。

Johnny\texttt{Johnny}Johnny 已经计算出了在理想条件下通过每条街道所需的时间(双向时间相同)。理想条件是指压路机在进入街道时已经在运动中,不需要加速或制动。但实际行驶中:

  1. 如果压路机在街道前或后改变方向,该街道的时间加倍
  2. 如果压路机从完全停止开始移动(如旅程开始时),该街道的时间加倍
  3. 如果压路机需要完全停止(如旅程结束时),该街道的时间加倍

给定城市大小 R×CR \times CR×C、起点坐标 (r1,c1)(r_1, c_1)(r1,c1)、终点坐标 (r2,c2)(r_2, c_2)(r2,c2) 以及每条街道的理想时间,求从起点到终点的最短时间。如果无法到达,输出“Impossible”。

数据范围1≤R,C≤1001 \leq R, C \leq 1001R,C100,街道时间不超过 100001000010000

题目分析

问题本质

这是一个加权最短路径问题,但路径代价的计算方式特殊。每条街道的实际通过时间不仅取决于街道本身的理想时间,还取决于压路机进入和离开该街道时的状态。

关键难点

  1. 状态依赖性:通过一条街道的时间取决于压路机进入街道时的状态(是否从停止开始)和离开街道时的状态(是否需要停止)
  2. 状态连续性:压路机可以连续通过多条街道而不停止,前提是方向不变
  3. 方向改变的成本:改变方向需要在交叉路口停止,这意味着额外的加速/减速成本

状态定义

我们需要一个状态表示压路机在交叉路口的情况。定义状态为三元组 (id,dir,cost)(id, dir, cost)(id,dir,cost),其中:

  • ididid:交叉路口的唯一标识,通过坐标 (r,c)(r, c)(r,c) 计算:id=(r−1)×C+cid = (r-1) \times C + cid=(r1)×C+c
  • dirdirdir:进入该路口时的方向状态:
    • dir=0dir = 0dir=0:压路机在当前路口已停止
    • dir=1dir = 1dir=1:从上方向进入(向下行驶)
    • dir=2dir = 2dir=2:从下方向进入(向上行驶)
    • dir=3dir = 3dir=3:从左方向进入(向右行驶)
    • dir=4dir = 4dir=4:从右方向进入(向左行驶)
  • costcostcost:到达该状态的最小时间代价

状态转移分析

从状态 (id,dir,cost)(id, dir, cost)(id,dir,cost) 出发,选择方向 ndir∈{1,2,3,4}ndir \in \{1, 2, 3, 4\}ndir{1,2,3,4} 移动到相邻路口 nidnidnid。设街道的理想时间为 ttt,则有以下三种转移情况:

情况 111:停止在下一个路口
  • 条件:总是可以选择停止
  • 时间计算
    • 如果 dir=0dir = 0dir=0(当前停止)或 dir≠ndirdir \neq ndirdir=ndir(改变方向):时间加倍,2t2t2t
    • 如果 dir=ndirdir = ndirdir=ndir(方向不变):时间加倍,2t2t2t(因为需要停止)
  • 新状态(nid,0,cost+2t)(nid, 0, cost + 2t)(nid,0,cost+2t)
情况 222:从停止开始运动且不停止
  • 条件dir=0dir = 0dir=0(当前停止)
  • 时间计算:从停止开始,时间加倍,2t2t2t
  • 新状态(nid,ndir,cost+2t)(nid, ndir, cost + 2t)(nid,ndir,cost+2t)
情况 333:继续运动不停止
  • 条件dir=ndirdir = ndirdir=ndir(方向不变)
  • 时间计算:连续运动,正常时间,ttt
  • 新状态(nid,ndir,cost+t)(nid, ndir, cost + t)(nid,ndir,cost+t)

算法设计

使用 Dijkstra\texttt{Dijkstra}Dijkstra 算法 求解最短路径,因为所有边权非负。算法步骤:

  1. 初始化

    • 起点状态:(startId,0,0)(startId, 0, 0)(startId,0,0),其中 dir=0dir=0dir=0 表示在起点停止
    • 距离数组 dist[id][dir]dist[id][dir]dist[id][dir] 初始化为无穷大
    • 优先队列(小根堆)存储待扩展状态
  2. 状态扩展

    • 从优先队列中取出代价最小的状态
    • 如果该状态已访问过(代价大于记录的最小值),跳过
    • 如果到达终点且 dir=0dir=0dir=0(停止状态),更新答案
    • 否则,尝试四个方向的转移,按照上述三种情况生成新状态
  3. 终止条件

    • 优先队列为空
    • 或找到目标状态(到达终点且停止)
  4. 答案输出

    • 如果找到有效路径,输出最小时间
    • 否则输出 Impossible

复杂度分析

  • 状态数O(R×C×5)O(R \times C \times 5)O(R×C×5),每个路口有5种方向状态
  • 每个状态的扩展:最多4个方向,每种方向最多3种转移
  • 总复杂度O(R×C×log⁡(R×C))O(R \times C \times \log(R \times C))O(R×C×log(R×C)),使用优先队列实现

输入处理技巧

题目输入格式特殊,需要按照特定顺序读取:

  1. 先读取 RRR 行横向街道时间(每行 C−1C-1C1 个值)
  2. 然后读取 R−1R-1R1 行纵向街道时间(每行 CCC 个值)

使用 map<pair<int, int>, int> 存储双向街道的时间,方便查询。

代码实现

// Steam Roller
// UVa ID: 1078
// Verdict: Accepted
// Submission Date: 2025-12-01
// UVa Run Time: 0.150s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

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

const int INF = 1e9;

// 方向:0-停止, 1-上, 2-下, 3-左, 4-右
int dr[] = {0, -1, 1, 0, 0};
int dc[] = {0, 0, 0, -1, 1};

// 将坐标转换为唯一ID
inline int getId(int r, int c, int C) { return (r - 1) * C + c; }

struct State {
    int id, dir, cost;
    bool operator<(const State& other) const { return cost > other.cost; }
};

int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
    int R, C, r1, c1, r2, c2, t;
    int caseNo = 1;
    while (cin >> R >> C >> r1 >> c1 >> r2 >> c2) {
        if (R == 0 && C == 0 && r1 == 0 && c1 == 0 && r2 == 0 && c2 == 0) break;
        // 使用map存储街道时间
        map<pair<int, int>, int> streetTime;
        // 按照题目描述的交替格式读取
        for (int i = 1; i <= R; i++) {
            // 读取第i行的横向街道(C-1个)
            for (int j = 1; j < C; j++) {
                cin >> t;
                if (t > 0) {
                    int id1 = getId(i, j, C), id2 = getId(i, j + 1, C);
                    streetTime[{id1, id2}] = streetTime[{id2, id1}] = t;
                }
            }
            // 如果不是最后一行,读取第i行的纵向街道(C个)
            if (i < R) {
                for (int j = 1; j <= C; j++) {
                    cin >> t;
                    if (t > 0) {
                        int id1 = getId(i, j, C), id2 = getId(i + 1, j, C);
                        streetTime[{id1, id2}] = streetTime[{id2, id1}] = t;
                    }
                }
            }
        }
        int startId = getId(r1, c1, C), endId = getId(r2, c2, C);
        // 距离数组:dist[id][dir]
        vector<vector<int>> dist(R * C + 5, vector<int>(5, INF));
        priority_queue<State> pq;
        dist[startId][0] = 0;
        pq.push({startId, 0, 0});
        int answer = INF;
        while (!pq.empty()) {
            State cur = pq.top(); pq.pop();
            int id = cur.id, dir = cur.dir, cost = cur.cost;
            // 将id转换回坐标
            int r = (id - 1) / C + 1, c = (id - 1) % C + 1;
            // 到达终点,必须是停止状态
            if (id == endId && dir == 0) {
                answer = min(answer, cost);
                continue;
            }
            // 尝试四个方向
            for (int ndir = 1; ndir <= 4; ndir++) {
                int nr = r + dr[ndir], nc = c + dc[ndir];
                if (nr < 1 || nr > R || nc < 1 || nc > C) continue;
                int nid = getId(nr, nc, C);
                // 检查街道是否存在
                auto it = streetTime.find({id, nid});
                if (it == streetTime.end()) continue;
                int timeNeeded = it->second;
                // 情况1:原来是停止,再次停止或者原来是运动,方向不变但停止
                int newCost = cost + 2 * timeNeeded;
                if ((dir == 0 || dir == ndir) && newCost < dist[nid][0]) {
                    dist[nid][0] = newCost;
                    pq.push({nid, 0, newCost});
                }
                // 情况2:从停止开始运动,到达交汇点不停止
                newCost = cost + 2 * timeNeeded;
                if (dir == 0 && newCost < dist[nid][ndir]) {
                    dist[nid][ndir] = newCost;
                    pq.push({nid, ndir, newCost});
                }
                // 情况3:继续之前的运动不停止
                newCost = cost + timeNeeded;
                if (dir == ndir && newCost < dist[nid][ndir]) {
                    dist[nid][ndir] = newCost;
                    pq.push({nid, ndir, newCost});
                }
            }
        }
        cout << "Case " << caseNo++ << ": ";
        if (answer == INF) cout << "Impossible" << endl;
        else cout << answer << '\n';
    }
    return 0;
}

总结

本题的关键在于正确建模压路机的状态,特别是区分停止状态和运动状态。通过将状态定义为 (id,dir,cost)(id, dir, cost)(id,dir,cost) 三元组,并明确三种状态转移情况,可以将问题转化为标准的单源最短路径问题,使用 Dijkstra\texttt{Dijkstra}Dijkstra 算法 求解。

核心要点

  1. 状态设计要包含方向信息
  2. 明确区分三种转移情况
  3. 终点必须是停止状态
  4. 注意输入格式的特殊性

这道题很好地展示了如何将实际问题中的复杂约束转化为图论模型,是状态空间搜索的经典例题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值