题目描述
Johnny\texttt{Johnny}Johnny 驾驶一辆蒸汽压路机回家。蒸汽压路机速度慢,启动、改变方向和完全停止都需要较长时间。城市街道呈矩形网格状布局,每个交叉路口与最多四个相邻路口通过街道连接。每条街道的长度正好是一个街区。
Johnny\texttt{Johnny}Johnny 已经计算出了在理想条件下通过每条街道所需的时间(双向时间相同)。理想条件是指压路机在进入街道时已经在运动中,不需要加速或制动。但实际行驶中:
- 如果压路机在街道前或后改变方向,该街道的时间加倍
- 如果压路机从完全停止开始移动(如旅程开始时),该街道的时间加倍
- 如果压路机需要完全停止(如旅程结束时),该街道的时间加倍
给定城市大小 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 1001≤R,C≤100,街道时间不超过 100001000010000。
题目分析
问题本质
这是一个加权最短路径问题,但路径代价的计算方式特殊。每条街道的实际通过时间不仅取决于街道本身的理想时间,还取决于压路机进入和离开该街道时的状态。
关键难点
- 状态依赖性:通过一条街道的时间取决于压路机进入街道时的状态(是否从停止开始)和离开街道时的状态(是否需要停止)
- 状态连续性:压路机可以连续通过多条街道而不停止,前提是方向不变
- 方向改变的成本:改变方向需要在交叉路口停止,这意味着额外的加速/减速成本
状态定义
我们需要一个状态表示压路机在交叉路口的情况。定义状态为三元组 (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=(r−1)×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 算法 求解最短路径,因为所有边权非负。算法步骤:
-
初始化:
- 起点状态:(startId,0,0)(startId, 0, 0)(startId,0,0),其中 dir=0dir=0dir=0 表示在起点停止
- 距离数组 dist[id][dir]dist[id][dir]dist[id][dir] 初始化为无穷大
- 优先队列(小根堆)存储待扩展状态
-
状态扩展:
- 从优先队列中取出代价最小的状态
- 如果该状态已访问过(代价大于记录的最小值),跳过
- 如果到达终点且 dir=0dir=0dir=0(停止状态),更新答案
- 否则,尝试四个方向的转移,按照上述三种情况生成新状态
-
终止条件:
- 优先队列为空
- 或找到目标状态(到达终点且停止)
-
答案输出:
- 如果找到有效路径,输出最小时间
- 否则输出
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)),使用优先队列实现
输入处理技巧
题目输入格式特殊,需要按照特定顺序读取:
- 先读取 RRR 行横向街道时间(每行 C−1C-1C−1 个值)
- 然后读取 R−1R-1R−1 行纵向街道时间(每行 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 算法 求解。
核心要点:
- 状态设计要包含方向信息
- 明确区分三种转移情况
- 终点必须是停止状态
- 注意输入格式的特殊性
这道题很好地展示了如何将实际问题中的复杂约束转化为图论模型,是状态空间搜索的经典例题。
247

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



