目錄
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,解題過程
跟着大佬敲的,沒什麼好說了。。。