题解:洛谷 P1346 电车
【题目来源】
洛谷:P1346 [CHCI 2002 Regional Competition Juniors] 电车
【题目描述】
在一个神奇的小镇上有着一个特别的电车网络,它由一些路口和轨道组成,每个路口都连接着若干个轨道,每个轨道都通向一个路口(不排除有的观光轨道转一圈后返回路口的可能)。在每个路口,都有一个开关决定着出去的轨道,每个开关都有一个默认的状态,每辆电车行驶到路口之后,只能从开关所指向的轨道出去,如果电车司机想走另一个轨道,他就必须下车切换开关的状态。
为了行驶向目标地点,电车司机不得不经常下车来切换开关,于是,他们想请你写一个程序,计算一辆电车从路口 \(A\) 到路口 \(B\),司机最少需要下车切换几次开关。
【输入】
第一行有 \(3\) 个整数 \(N,A,B\)(\(2 \leq N \leq 100, 1 \leq A,B \leq N\)),分别表示路口的数量,和电车的起点,终点。
接下来有 \(N\) 行,每行的开头有一个数字 \(K_i\)(\(0 \leq K_i \leq N-1\)),表示这个路口与 \(K_i\) 条轨道相连,接下来有 \(K_i\) 个数字表示每条轨道所通向的路口,开关默认指向第一个数字表示的轨道。
【输出】
输出文件只有一个数字,表示从 \(A\) 到 \(B\) 所需的最少的切换开关次数,若无法从 \(A\) 前往 \(B\),输出 \(-1\)。
【输入样例】
3 2 1
2 2 3
2 3 1
2 1 2
【输出样例】
0
【核心思想】
-
问题分析:给定 \(N\) 个路口的有向图,每个路口有 \(K_i\) 条出边,其中第一条为默认轨道(无需切换,边权 \(0\)),其余轨道均需切换开关(边权 \(1\))。求从路口 \(A\) 到路口 \(B\) 的最少切换次数。这是一个边权为 \(0\) 或 \(1\) 的最短路径问题,可用 SPFA 或 0-1 BFS 求解。
-
算法选择:
- SPFA(Shortest Path Faster Algorithm):基于 Bellman-Ford 的队列优化,适合处理边权非负的一般图最短路
- 边权建模:默认轨道边权为 \(0\),非默认轨道边权为 \(1\),将"切换次数"转化为图上的带权最短路
-
关键步骤:
- 初始化:读取 \(N\)(路口数)、\(A\)(起点)、\(B\)(终点)
- 建图(遍历每个路口 \(i\)):
- 读取 \(K_i\)(出边数量)
- 读取 \(K_i\) 个目标路口,第 \(1\) 个边权为 \(0\),其余边权为 \(1\),加入邻接表 \(g[i]\)
- SPFA 求最短路:
- 初始化 \(dist[1..N] = \infty\),\(dist[A] = 0\),起点 \(A\) 入队
- 当队列非空时:
- 取出队首 \(u\)
- 遍历 \(u\) 的所有出边 \((v, w)\):
- 松弛操作:若 \(dist[v] > dist[u] + w\),则更新 \(dist[v] = dist[u] + w\),并将 \(v\) 入队
- 输出答案:若 \(dist[B] = \infty\) 输出 \(-1\),否则输出 \(dist[B]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(k \cdot E)\),其中 \(k\) 为平均入队次数,\(E\) 为总边数;在边权为 \(0/1\) 的特殊图中效率接近 \(O(E)\)
- 空间复杂度:\(O(N + E)\),邻接表存储 + 距离数组 + 队列
-
SPFA 的核心思想:
- 队列优化 Bellman-Ford:只有当一个节点的最短距离被更新时,它的出边才可能影响其他节点,因此仅将更新过的节点入队
- 松弛传播:通过队列将"距离变短"的信息逐层传播到全图,避免对未更新节点做无效松弛
- 0-1 边权特性:本题边权仅为 \(0\) 或 \(1\),也可用双端队列优化的 0-1 BFS 达到 \(O(E)\) 的严格复杂度
- 适用场景:边权非负且存在负权边的一般图最短路,或稠密图中的单源最短路问题
【算法标签】
普及 #SPFA
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII; // 定义边信息对:first为终点,second为边权(0或1)
const int N = 105; // 最大路口数量
int n, a, b; // n:路口数量, a:起点, b:终点
vector<PII> g[N]; // g[i]:从路口i出发的所有边
int dist[N]; // dist[i]:从起点到路口i的最少切换次数
void spfa()
{
queue<int> q; // SPFA算法的队列
memset(dist, 0x3f, sizeof(dist)); // 初始化距离为无穷大
dist[a] = 0; // 起点到自身的距离为0
q.push(a); // 起点入队
while (!q.empty())
{
auto u = q.front(); q.pop(); // 取出队首路口
for (auto t : g[u]) // 遍历从u出发的所有轨道
{
int v = t.first, w = t.second; // v:目标路口, w:是否需要切换开关(0或1)
// 松弛操作:如果从u到v的切换次数更少,则更新距离
if (dist[v]> dist[u] + w)
{
dist[v] = dist[u] + w; // 更新到v的最短距离
q.push(v); // v入队,继续松弛
}
}
}
}
int main()
{
cin >> n >> a >> b; // 读入路口数、起点、终点
for (int i=1; i<=n; i++) // 读入每个路口的轨道信息
{
int k; cin >> k; // k:该路口连接的轨道数量
for (int j=1; j<=k; j++)
{
int x; cin >> x; // x:该轨道通向的路口
// 第一条轨道是默认轨道,无需切换(边权为0)
// 其他轨道需要切换开关(边权为1)
if (j==1) g[i].push_back({x, 0});
else g[i].push_back({x, 1});
}
}
spfa(); // 运行SPFA求最短路
// 如果终点不可达,输出-1;否则输出最少切换次数
if (dist[b]==0x3f3f3f3f)
cout << -1 << endl;
else
cout << dist[b] << endl;
return 0;
}
【运行结果】
3 2 1
2 2 3
2 3 1
2 1 2
0
浙公网安备 33010602011771号