把(楼层,手柄槽位)作为图上节点,扳槽与电梯移动拆成两类边权,Dijkstra 求从(1 层,0 号槽)到 N 层的最短时间。
OJ: roj
题目 ID: 3210
难度:提高
标签:图论最短路Dijkstra
创建: 2026-10-02 01:57
更新: 2026-10-10 11:30
形式化题目
给出一个
一次操作可以选择:
- 把手柄扳到相邻槽
,耗时 秒(楼层不变); - 扳动到槽
之后再按动电梯:沿槽 移动 层( 上行、 下行),耗时 秒;目标楼层必须仍在 内,否则该移动不合法。
初始状态是
数据范围:
输入样例 6 3 与 -1 0 2 的最优路线如下(括号内是 (楼层, 槽位)):
总耗时
正解
思路
为什么这是最短路问题。「最快到达」的每一步都只有固定代价(扳槽
**状态必须带槽位,只记楼层是错的。**到达第
**边怎么连。**从状态
| 操作 | 新状态 | 边权 |
|---|---|---|
| 扳到槽 |
||
| 沿槽 |
两点说明:
- 「扳到槽
」不必按相邻槽一格一格建模。从 扳到 必须经过中间每个槽、每格 秒,总代价就是 ,直接连一条边是等价的压缩。中间槽位只是「路过」,停留时间为零,不会产生额外决策。 - 「扳槽」和「移动电梯」合并成一条边
,权为 ,与先扳后走完全等价(先扳到 再走,中间状态 的最短路一定会被这条合并边覆盖)。本题解采用合并边,每层楼只需枚举 条出边。
**边权非负,用 Dijkstra。**所有边权都
下面按样例把这张状态图画出来(只画最优路径经过的节点,边上的数字是这条合并边的权值):
flowchart LR
A["(1层, 槽0)<br/>C=0"] -->|"1+4=5"| B["(3层, 槽2)<br/>C=2"]
B -->|"0+4=4"| C["(5层, 槽2)<br/>C=2"]
C -->|"2+2=4"| D["(4层, 槽0)<br/>C=-1"]
D -->|"2+4=6"| E["(6层, 槽2)<br/>C=2"]
style A fill:#e8f4e8
style E fill:#ffe0e0
观察这张图:绿色起点是电梯的初始状态(
实现细节。
- 距离表用
, , ;初始槽 是 中值为 的下标。 - Dijkstra 弹出节点时若
可以直接返回:第一次被弹出说明该距离已确定,是到 层的最短时间(到 层任意槽都算终点)。 - 到不了的判定:堆空仍未弹出
层,输出 。注意样例 1 中 会让电梯掉到 层,凡是会滑出 的移动都要跳过。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
int n, m;
int slots[25]; // 各槽位的 C 值
int start_slot; // 手柄初始槽(C=0)
ll dist[1005][25]; // dist[楼层][槽位]
void solve() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> slots[i];
if (slots[i] == 0) start_slot = i;
}
for (int i = 1; i <= n; i++)
for (int j = 0; j < m; j++)
dist[i][j] = INF;
dist[1][start_slot] = 0;
priority_queue<pair<ll, pair<int, int> >,
vector<pair<ll, pair<int, int> > >,
greater<pair<ll, pair<int, int> > > > pq;
pq.push(make_pair(0LL, make_pair(1, start_slot)));
while (!pq.empty()) {
ll d = pq.top().first;
int floor = pq.top().second.first;
int slot = pq.top().second.second;
pq.pop();
if (floor == n) { // 第一次弹出 N 层即最优
cout << d << "\n";
return;
}
if (d > dist[floor][slot]) continue;
for (int j = 0; j < m; j++) { // 扳到槽 j
int nf = floor + slots[j];
if (nf < 1 || nf > n) continue;
ll nd = d + (ll)abs(slot - j) + 2LL * (ll)abs(slots[j]);
if (nd < dist[nf][j]) {
dist[nf][j] = nd;
pq.push(make_pair(nd, make_pair(nf, j)));
}
}
}
cout << "-1\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 01:57
# update_at: 2026-10-02 01:57
import heapq
import sys
from collections import defaultdict
from collections.abc import Iterator
INF = float("inf") # dist 里“还没到过”的哨兵
def neighbors(state: tuple[int, int], slots: list[int], top: int) -> Iterator[tuple[int, tuple[int, int]]]:
"""从状态 (楼层, 槽位) 出发,依次产出 (边权, 新状态)。
扳槽只改槽位,走电梯只改楼层,所以邻边天然分两类。
"""
floor, slot = state
for j in range(len(slots)): # 扳到槽 j:|槽差| 秒,电梯跟着动 |C_j| 层
nf = floor + slots[j]
if 1 <= nf <= top:
yield abs(slot - j) + 2 * abs(slots[j]), (nf, j)
def shortest_time(slots: list[int], top: int) -> int:
"""从 (1 层, 初始槽) 到 N 层任意槽位的最短时间;到不了返回 -1。"""
start = (1, slots.index(0)) # 手柄最初停在值为 0 的槽
dist = defaultdict(lambda: INF)
dist[start] = 0
pq = [(0, start)]
while pq:
d, state = heapq.heappop(pq)
if state[0] == top: # 第一次弹出 N 层即最优
return d
if d > dist[state]:
continue
for w, nxt in neighbors(state, slots, top):
if d + w < dist[nxt]:
dist[nxt] = d + w
heapq.heappush(pq, (d + w, nxt))
return -1
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
m = next(data) # 题面的 M
slots = [next(data) for _ in range(m)]
print(shortest_time(slots, n))
if __name__ == "__main__":
solve()复杂度
- 节点数
,每个节点出边 条,边数 。 - 堆优化 Dijkstra 的时间复杂度为
,约 级别;空间复杂度 。在 、 的数据范围内非常宽裕。
总结
- 本题的关键一步是把「电梯的瞬间」建模成图节点:状态
里的槽位 不能丢,否则「扳槽代价」无从计算。 - 两类操作(逐格扳槽、沿
移动)可以各自连边,也可以合并成一条 的边;两种建模等价,边数相差一倍。 - 边权全部非负,直接套堆优化 Dijkstra;「越界不合法」就是图上不存在这条边,堆跑空即
。