题目描述
从起点乘车到机场,路线有经济路线和商业路线,仅有一张商业线票,仅能坐一站的商业线,求最快路线。
Input
输入包含多组数据。每组数据第一行为 3 个整数 N, S 和 E (2 ≤ N ≤ 500, 1 ≤ S, E ≤ 100),即猫猫快线中的车站总数,起点和终点(即喵星机场所在站)编号。
下一行包含一个整数 M (1 ≤ M ≤ 1000),即经济线的路段条数。
接下来有 M 行,每行 3 个整数 X, Y, Z (1 ≤ X, Y ≤ N, 1 ≤ Z ≤ 100),表示 TT 可以乘坐经济线在车站 X 和车站 Y 之间往返,其中单程需要 Z 分钟。
下一行为商业线的路段条数 K (1 ≤ K ≤ 1000)。
接下来 K 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。
Output
对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
解题思路
首先,这肯定是一道单源最短路问题,边权就是时间,但问题在于它有两套路线:经济线与商业线,要求商业线最多选择一条,且保证了最优解唯一,直观的想法就是到底选不选一条商业线,根据这个选择来求解;
如果不选择,那说明通过经济线一定可以到达终点,那直接一次单源最短路就能求解,但如果仅通过经济线无法到达重点,则说明一定要选一条商业线,选择哪一条商业线才能使得整条路径最短?不妨每一条商业线都选一次,每次都比对一下,如果选择了这条商业线可以使得总路径最短,那我们就选择这条商业线,这个想法很自然。基于此,将整个程序的流程分为以下过程:
- 输入经济线路径,根据经济线分别以起点和终点跑两次dijkstra,就可以记录起点到任一点i的最短路长和i到重点的最短路长,同时记录路径点;
- 令ans=dis1[终点],记录当前只选择经济线的情况下的最短路长(不连通就是maxn);
- 输入商业路线,对每一条商业路线进行选择,判断是否进行路径的更新,设该条商业线的起始点分别为u、v,边权为w,若选择这条商业线,则最总路长就会变为dis1[u]+dis2[v]+w(dis1[v]+dis2[u]+w,因为交通路线是无向的),通过比较对ans的大小进行更新,若选择该条商业线,则记录(或更新)这条商业线的起始点(因为只能选一条商业线);
- 判断ans是否有变化来知道是否选择了商业线,再根据情况来输出路径的点,输出时要注意不同位置的点的输出顺序。
实现代码
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int maxn=1e7;
struct edge
{
int next,time;
};
vector<edge> G[1010];
int N,S,E,M,K,ans=0,busp=0,buss,buse;
int dis1[510]={maxn},dis2[510]={maxn},path1[510],path2[510];
bool vis[510];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
void dijkstra(int s,int* ans,int* path)
{
while(!q.empty())
q.pop();
q.push(make_pair(0,s));
ans[s]=0;
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(vis[u]==false)
{
vis[u]=true;
for(int i=0;i<G[u].size();++i)
{
int v=G[u][i].next,w=G[u][i].time;
if(ans[v]>ans[u]+w)
{
ans[v]=ans[u]+w;
path[v]=u;
q.push(make_pair(ans[v],v));
}
}
}
}
}
void path_output1(int p)
{//递归倒序输出路径 :从S到p
if(path1[p]==0)
cout<<p;
else
{
path_output1(path1[p]);
cout<<" "<<p;
}
}
void path_output2(int p)
{//顺序输出路径 :从p到E
if(path2[p]==0)
cout<<" "<<p;
else
{
cout<<" "<<p;
path_output2(path2[p]);
}
}
void clear()
{
for(int i=1;i<=N;++i)
{
dis1[i]=maxn;
dis2[i]=maxn;
path1[i]=0;
path2[i]=0;
vis[i]=false;
ans=0;
G[i].clear();
}
}
int main()
{
bool f=false;
while(cin>>N>>S>>E)
{
if(f)
cout<<endl;
cin>>M;
clear();
for(int i=0;i<M;++i)
{
int u,v,w;
cin>>u>>v>>w;
edge x,y;
x.next=v;
x.time=w;
y.next=u;
y.time=w;
G[u].push_back(x);
G[v].push_back(y);
}
dijkstra(S,dis1,path1);
for(int i=1;i<=N;++i)
vis[i]=false;
dijkstra(E,dis2,path2);
ans=dis1[E];
cin>>K;
for(int i=0;i<K;++i)
{
int u,v,w;
cin>>u>>v>>w;
if(ans>dis1[u]+w+dis2[v])
{
ans=dis1[u]+w+dis2[v];
buss=u;
buse=v;
}
else if(ans>dis1[v]+dis2[u]+w)
{
ans=dis1[v]+dis2[u]+w;
buss=v;
buse=u;
}
}
if(dis1[E]<=ans)
{//不走商业线,从S经济线直达E
path_output1(E);
cout<<endl<<"Ticket Not Used"<<endl;
cout<<dis1[E]<<endl;
}
else
{//必须走商业线
path_output1(buss);
path_output2(buse);
cout<<endl<<buss<<endl;
cout<<ans<<endl;
}
f=true;
}
return 0;
}
总结
这是一道单源最短路的变形题,难点在于如何选择一条商业线,这里使用的办法是,对每一条商业线都进行尝试,比较最短路长是否有变化进行选择,若选择了就设置“断点”,遍历结束后就得到了最短路长,再根据断点的位置来进行点的输出,而设置断点的前提是从起点和终点分别跑两次迪杰斯特拉算法,记录相关信息。