将同一线路所有前后站点对连单向权值 1 边转化为无权图单源最短路,通过 BFS 求解最少乘车次数。
OJ: roj
题目 ID: 1377
难度:普及+/提高-
标签:图论广度优先搜索最短路
创建: 2026-09-30 07:43
更新: 2026-10-07 11:01
形式化题目
给定
若无法直达,可在同一站点下车并转乘经过该站的其他线路。求从站点 NO。
正解:转换为单位权图的最短路 (BFS)
思路
1. 问题转化与建图
换车次数的本质是计算从起点到终点最少乘坐了几趟车。
- 若总共乘坐了
趟车,则换车次数恰好为 。 - 如果无需乘车(例如起点即为终点),换车次数为
。
在同一条巴士线路中,如果站点
2. 最短路求解
经过上述建图后,问题转化为:在一个所有边权均为
由于边权全为
- 初始化距离数组
为 , ,将起点 入队。 - 依次取出队头节点
,遍历 的所有出边 。若 ,则令 并将 入队。 - 当队列为空或遍历到终点
时搜索结束。
最终,若 NO;否则最少换车次数为
代码
Python 版:
python
#!/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-09-30 07:43
# update_at: 2026-09-30 07:43
from collections import deque
import sys
def solve() -> None:
lines = [line.strip() for line in sys.stdin if line.strip()]
if not lines:
return
m, n = map(int, lines[0].split())
# 同一条线路中,前面的站点都能花 1 次乘车到达后面的站点
adj: list[list[int]] = [[] for _ in range(n + 1)]
for line in lines[1 : m + 1]:
stops = list(map(int, line.split()))
for i in range(len(stops)):
for j in range(i + 1, len(stops)):
adj[stops[i]].append(stops[j])
# BFS 求从 1 到 n 的最少乘车次数(边权全为 1)
dist = [-1] * (n + 1)
dist[1] = 0
q = deque([1])
while q:
u = q.popleft()
if u == n:
break
for v in adj[u]:
if dist[v] == -1:
dist[v] = dist[u] + 1
q.append(v)
# 乘车次数减 1 即为换乘次数;不可达则输出 NO
print("NO" if dist[n] == -1 else max(0, dist[n] - 1))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* 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-05 00:06
* update_at: 2026-10-05 00:06
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 505;
typedef long long ll;
int m, n;
bool conn[MAXN][MAXN]; // conn[u][v] = true 表示 u 可以花 1 次乘车直达 v(同一线路前后站点对)
int dist[MAXN]; // dist[v] 表示从站点 1 到站点 v 的最少乘车次数,-1 表示未访问
int que[MAXN]; // BFS 手写队列,存站点编号
// 读入所有线路,把同一线路里的每对前后站点连成一条权值为 1 的有向"乘车"关系。
void read_input() {
cin >> m >> n;
// 注意每条线路一行,站点个数不定,整行读入后拆分
string line;
getline(cin, line); // 吃掉第一行末尾换行
for (int i = 1; i <= m; i++) {
getline(cin, line);
istringstream iss(line);
int cnt = 0;
int stops[MAXN];
while (iss >> stops[cnt + 1]) cnt++;
// 同一线路上任意前面的站点都能直达后面的站点,乘一次车
for (int a = 1; a <= cnt; a++) {
for (int b = a + 1; b <= cnt; b++) {
conn[stops[a]][stops[b]] = true;
}
}
}
}
// BFS 求站点 1 到站点 n 的最少乘车次数(边权全为 1)。
void bfs() {
for (int v = 1; v <= n; v++) dist[v] = -1;
int head = 1, tail = 1;
dist[1] = 0;
que[tail] = 1;
tail++;
while (head < tail) {
int u = que[head];
head++;
for (int v = 1; v <= n; v++) {
if (conn[u][v] && dist[v] == -1) {
dist[v] = dist[u] + 1;
que[tail] = v;
tail++;
}
}
}
}
void solve() {
bfs();
// 乘车次数减 1 就是换车次数;不可达输出 NO
if (dist[n] == -1)
cout << "NO" << endl;
else
cout << max(0, dist[n] - 1) << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
solve();
return 0;
}复杂度
- 时间复杂度:
,其中 为线路条数, 为单条线路经过的最多站点数( )。建图时对每条线路枚举所有有序点对需要 ;BFS 遍历每个点和每条边至多一次,耗时 。在 范围内运行效率极高。 - 空间复杂度:
,用于存储图的邻接表以及 BFS 队列和距离数组。
总结
- 乘车问题建图范式:公交线路问题通常有两种建图视角:一种是“点是车站,同线所有前后站点对直接连边”;另一种是“拆点/线转点(以线路为点或分层图)”。本题中站点数
较小,直接对同线前后站点全部连有向边最为直接直观。 - 权值为 1 的最优解模型:换乘代价固定时,最小化换乘即为最小化乘车次数,图上每条边权为 1,直接用 BFS 即可保证最优性。
图示解析
以样例数据为例:
- 线路 1:
6 -> 7 - 线路 2:
4 -> 7 -> 3 -> 6 - 线路 3:
2 -> 1 -> 3 -> 5
flowchart LR
subgraph Line3["线路 3"]
S2((2)) --> S1((1))
S1 --> S3((3))
S3 --> S5((5))
S1 -.直达.-> S5
end
subgraph Line2["线路 2"]
S4((4)) --> S7((7))
S7 --> S3_2((3))
S3_2 --> S6((6))
S4 -.直达.-> S6
S7 -.直达.-> S6
end
subgraph Line1["线路 1"]
S6_2((6)) --> S7_end((7))
end
S3 ===|站点 3 换乘| S3_2
S6 ===|站点 6 换乘| S6_2
classDef hotel fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
classDef park fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
class S1 hotel;
class S7_end park;
乘车路径选择为:
- 在站点
乘坐线路 3 直达站点 (乘车 次); - 在站点
下车换乘线路 2,坐到站点 (乘车第 次,换乘 次); - 在站点
下车换乘线路 1,坐到终点 (乘车第 次,换乘第 次)。
总乘车次数为