目录
1,题目描述
Sample Input:
4
7 1001 3212 1003 1204 1005 1306 7797
9 9988 2333 1204 2006 2005 2004 2003 2302 2001
13 3011 3812 3013 3001 1306 3003 2333 3066 3212 3008 2302 3010 3011
4 6666 8432 4011 1306
3
3011 3013
6666 2001
2004 3001
Sample Output:
2
Take Line#3 from 3011 to 3013.
10
Take Line#4 from 6666 to 1306.
Take Line#3 from 1306 to 2302.
Take Line#2 from 2302 to 2001.
6
Take Line#2 from 2004 to 1204.
Take Line#1 from 1204 to 1306.
Take Line#3 from 1306 to 3001.
题目大意
给出几条地铁线路,以及若干对起点和终点,要求输出起点到终点的最短路径(路过的站点数目最少,若相同,则选择换乘次数最少的)。
2,思路
参考大神的解法@日沉云起【pat甲级1131 Subway Map (30 point(s))】
数据结构
- int dis[10005]:存放起点到各个站点的最短距离(即最少经过的站点数目);
- unordered_map<int, int> line:key:一段路(每段路有两个端点v1,v2,用一个整型变量v1*10000+v2表示v1->v2,v2*10000+v1表示v2->v1) 。value:属于几号线(每段路只属于一条线);
- vector<int> graph[10005]:图的邻接表表示法,与i站点相邻的站点编号存放至graph[i]对应的vector中;
- vector<int> pre[10005]:存放起点到所有节点的最短路径(每个节点的前驱节点)。准确来说并不是所有节点,一旦距离超过dis[dest]便不再继续计算后面的节点;
- vector<int> path:存放最短路径中的所有节点;
- vector<pair<int, int>> trans:first换乘站编号、second"换乘站->下一站"所属线路;
算法
- 接受数据构建无向邻接表图,并记录每段路(int型变量表示,具体见数据结构中描述)所属线路:
- 每对查询的始终点使用一次BFS求出起点到终点的所有最短路径,存入pre中,最短路径的具体值存放至dis:
- 多所有的最短路径使用一次DFS,求出最终答案path。遍历path用trans记录换乘节点及路线:
说明
trans:
3,AC代码
#include<bits/stdc++.h>
using namespace std;
int N, M, start, dest, dis[10005]; //N线路数目 M查询数目 start查询起点 dest目的地
unordered_map<int, int> line; //key:一段路 value:属于几号线(每段路只属于一条线)
vector<int> graph[10005], pre[10005], path; //graph图、pre最短路径中每个节点的前驱节点、path最终路径(含每个节点)
vector<pair<int, int>> trans; //换乘站编号、"换乘站->下一站"所属线路
void dfs(int stop){ //因为最短路径记录的是前驱节点 所以遍历时需要从终点向起点遍历
path.push_back(stop);
if(stop == start){ //出口条件
vector<pair<int, int>> tempTrans;
tempTrans.push_back({start, -1}); //存放起点 没有任何站点到达起点 所以将线路设为-1
for(int i = path.size()-2; i > 0; i--){ //path存放的最短路径是从终点到起点的 所以从后向前遍历
if(line[path[i]*10000 + path[i-1]] != line[path[i]*10000 + path[i+1]])//i->i-1 与 i->i+1 线路不一致 即i为换乘站
tempTrans.push_back({path[i], line[path[i]*10000 + path[i+1]]});//记录换乘站即换乘线路 !!!是i+1 不是i-1
}
tempTrans.push_back({dest, line[path[0]*10000 + path[1]]}); //存放终点
if(trans.size() == 0 || trans.size() > tempTrans.size()) // !!!注意起始trans为空的情况
trans = tempTrans;
}
for(auto i : pre[stop])
dfs(i);
path.pop_back();
}
void bfs(){
for(int i = 0; i < 10005; i++){
pre[i].clear();
dis[i] = INT_MAX;
}
queue<int> q;
q.push(start);
dis[start] = 0;
while(!q.empty()){
int p = q.front();
q.pop();
if(dis[p] > dis[dest]) // !!!剪枝
continue;
for(int i : graph[p]){ // !!!妙啊 i即与p相邻的节点编号(而不是下标)
if(dis[i] >= dis[p] + 1){
if(dis[i] == INT_MAX) // !!!妙啊 dis[i]为最大值 就说明节点未被访问过 从而加入队列
q.push(i);
if(dis[i] > dis[p] + 1){
dis[i] = dis[p] + 1;
pre[i].clear(); //最短路径改变 清除以前记录的前驱节点
}
pre[i].push_back(p);
}
}
}
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen("1.txt", "r", stdin);
#endif // ONLINE_JUDGE
scanf("%d", &N);
int num, stop1, stop2;
for(int i = 1; i <= N; i++){
scanf("%d%d", &num, &stop1); // !!!
for(int j = 1; j < num; j++, stop1 = stop2){ // !!!妙啊 还可以这样j++, stop1 = stop2
scanf("%d", &stop2);
line[stop1*10000+stop2] = i; // !!!妙啊 用整型stop1*10000+stop2表示stop1->stop2
line[stop2*10000+stop1] = i;
graph[stop1].push_back(stop2); // 无向图 邻接表存储
graph[stop2].push_back(stop1);
}
}
scanf("%d", &M);
while(M--){ // !!!后自减 不是前自减
scanf("%d%d", &start, &dest);
bfs();
path.clear(); //最短路径记录
trans.clear(); //换乘站记录
dfs(dest);
printf("%d\n", dis[dest]); // !!!
for(int i = 1; i < trans.size(); i++)
printf("Take Line#%d from %04d to %04d.\n", trans[i].second, trans[i-1].first, trans[i].first);// !!!妙啊 别忘了first
}
return 0;
}
4,解题过程
跟着大佬敲的,没什么好说了。。。