【题目描述】
H城是一个旅游胜地,每年都有成千上万的人前来观光。为方便游客,巴士公司在各个旅游景点及宾馆,饭店等地都设置了巴士站并开通了一些单程巴士线路。每条单程巴士线路从某个巴士站出发,依次途经若干个巴士站,最终到达终点巴士站。
一名旅客最近到H城旅游,他很想去S公园游玩,但如果从他所在的饭店没有一路巴士可以直接到达S公园,则他可能要先乘某一路巴士坐几站,再下来换乘同一站台的另一路巴士, 这样换乘几次后到达S公园。
现在用整数1,2,...N 给H城的所有的巴士站编号,约定这名旅客所在饭店的巴士站编号为1,S公园巴士站的编号为N。
写一个程序,帮助这名旅客寻找一个最优乘车方案,使他在从饭店乘车到S公园的过程中换车的次数最少。
【输入】
第一行有两个数字M和N(1≤M≤100,1<N≤500),表示开通了M条单程巴士线路,总共有N个车站。从第二行到第M行依次给出了第1条到第M条巴士线路的信息。其中第i+1行给出的是第i条巴士线路的信息,从左至右按运行顺序依次给出了该线路上的所有站号相邻两个站号之间用一个空格隔开。
【输出】
只有一行。如果无法乘巴士从饭店到达S公园,则输出"NO",否则输出你的程序所找到的最少换车次数,换车次数为0表示不需换车即可到达。
【输入样例】
3 7
6 7
4 7 3 6
2 1 3 5
【输出样例】
2
1. 题目背景与分析
题目核心 :给定M条单向巴士线路,每条线路包含若干个站点。求从起点(站点1)到终点(站点N)的最少换乘次数。
关键难点:
-
输入格式:每行巴士线路的站点数量不固定,无法通过固定的循环次数读取。
-
建图逻辑:巴士是单向的,且"直达"意味着在一号线上,站点A如果在站点B前面,那么A可以直接到 B,同时不需要中间停靠算作多步,但是B不可以直接到A,。
-
结果转换:题目求的是"换乘次数",而 BFS 求的是"坐车次数"或"路径长度",需要进行数学转换。
2. 核心技术点解析
A. 不定长输入的处理 (stringstream)
这是本题的一大难点。由于每行站点数未知,使用cin很难判断换行。
解决方案:先用 getline 读取整行字符串,再用 stringstream 将字符串转换为输入流,像"切香肠"一样把数字一个个取出来。
cpp
string s;
getline(cin,s); // 读入一整行
stringstream ss(s); // 放入流中
int x;
while(ss>>x){ // 自动按空格切割读取
vec[i].push_back(x);
}
B. 建图模型
如果一条线路是 1 -> 3 -> 6 -> 7,意味着什么?
-
1 能到 3
-
1 能到 6
-
1 能到 7
-
3 能到 6 ...
只要是在同一条线上,前面的站都可以直达后面的站,且代价(坐车次数)都为 1。我们需要双重循环来建立这些单向边。
C. 答案公式推导
BFS 求出的 vis[n] 是目标节点的深度(也就是经过的节点数,包含起点)。
-
路径长度 (坐车次数) = 深度 - 1
-
换乘次数 = 坐车次数 - 1
-
最终公式 :
ans=vis[n] - 2
3. 完整代码
cpp
//因为点与点之间距离为1,所以bfs或者dij都可以
#include <iostream>
#include <sstream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
int m,n;
int g[510][510];
vector<int> vec[110];
int vis[510];//记录换乘次数
queue<int> q;
void bfs(int start){
q.push(start);
vis[start]=1;//起点打上标记
while(!q.empty()){
int tmp=q.front();//访问队首元素
q.pop();//队首出队
if(tmp==n) return;//如果已经搜到终点,就可以退出了
//遍历所有可能的下一个车站 如果和tmp距离为1且没有访问过就入队并打上标记
for(int i=1;i<=n;i++){
if(g[tmp][i]==1 && vis[i]==0){
q.push(i);
vis[i]=vis[tmp]+1;
}
}
}
}
int main(){
cin>>m>>n;
//cin读取完n后,光标停留在读取n结束后的换行符之前,
//把第一行n后面的换行符空格等要先读取掉
string kong;
getline(cin, kong);
//然后把每行不定长数据通过字符串流读取进去
string s;
for(int i=1;i<=m;i++){
getline(cin,s);//读整行
stringstream ss(s);//放入流中
int x;
while(ss>>x){//自动按空格切割读取
vec[i].push_back(x);
}
}
//初始化所有车站间距离为无穷
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=0x3f3f3f3f;
}
}
for(int i=1;i<=n;i++) g[i][i]=0;//所有车站到自己距离为0
//邻接矩阵存图,这里的距离就代表换乘次数
for(int i=1;i<=m;i++){//遍历vector每一行,每一行代表一条巴士线路,所有点互相之间都有边
for(int j=0;j<vec[i].size();j++){//一行,遍历所有车站
for(int k=j+1;k<vec[i].size();k++){//j与k之间有边
g[vec[i][j]][vec[i][k]]=1;//单向的
}
}
}
bfs(1);
//输出
if(vis[n]==0) cout<<"NO";
//深度vis[n]包括了起点本身
//坐车次数=vis[n]-1
//换乘次数=坐车次数-1=vis[n]-2
else cout<<vis[n]-2;
return 0;
}
4. 易错点总结
-
输入读取 :如果不加
getline(cin, kong)吃掉第一行的换行,后续读取会错位,而且不能用getchar()读取一个,必须用getline吃一行因为末尾可能还有空格然后才是回车。 -
单向边 :题目强调是"单程巴士",千万不能建立
g[后][前]=1的反向边。 -
结果计算 :很多同学会想当然觉得输出
vis[n]-1,但这算的是坐了几趟车,题目问的是换了几次,必须再减 1。