问题描述:
众所周知,TT 有一只魔法猫。
今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。 TT 从家里出发,准备乘坐猫猫快线前往喵星机场。猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。当然啦,商业线要比经济线贵,TT 平常只能坐经济线,但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,不然就要误机了!
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 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
样例数入:
4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3
样例输出:
1 2 4
2
5
解题思路:
首先通过分析题目,这是一个求解两点之间最短路的距离,则我们可以使用Dijkstra来求解。其中最关键的一步为松弛操作,将已经松弛的点放入最小堆中,每次中最小堆中取出距离最小的一个点,一旦一个点从最小堆中弹出,则该点的距离到达了最小值。然后通过取出来的点,编译它的所有邻接点,进行松弛。
由于题目中牵扯到商业线,且只能乘坐一次。所以我们可以通过枚举每一条商业线,计算起点到u的最短路以及v到 终点的最短路再加上该商业线所花费的时间。故我们可以在经济线上求解起点到终点的最短路,然后求解从终点到起点的最短路,然后通过枚举每一条商业线,得到走该条商业线的最小值,然后和不走商业线进行比较。由于该图是无向图,所以走这条商业线有两种走法,一种是从起点到商业线的起点,另一种是终点到商业线的起点。
对于实现,由于要用两次Dijkstra,所以使用了完全相同的两种数组。由于最后要输出所走的线路,所以在Dijkstra算法的基础上又增加的一个数组pre,用来找该节点的前一个节点,通过pre数组,就能找到一条完整的路径。
并且注意,该题对输出格式有很大的要求,一定要仔细读题,考虑好输出的格式。
代码:
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
struct point{//结构体,有一条边的终点和权值两域
int end;
int time;
bool operator < (const point &p)const{//重载<,供堆使用
return time>p.time;
}
};
int N,S,E,M,K,min_path;
int vis1[510];//两次Dijkstra用来标记一个点是否在堆中
int vis2[510];
int pre1[510];//两次Dijkstra分别记录一个点的前驱
int pre2[510];
int dis1[510];//两次Dijkstra一个点到原点的距离
int dis2[510];
int main()
{
bool huanh=false;
while(cin>>N>>S>>E)
{
if(huanh)
cout<<endl;
huanh=true;
for(int i=0;i<510;i++)//初始化
vis1[i]=vis2[i]=pre1[i]=pre2[i]=0;
vector<point> line[510];
cin>>M;
for(int i=0;i<M;i++)
{
int x,y,z;
cin>>x>>y>>z;
point temp;
temp.end=y;
temp.time=z;
line[x].push_back(temp);//构造无向图
temp.end=x;
line[y].push_back(temp);
}
priority_queue<point> q1,q2;//最小堆
for(int i=1;i<=N;i++)
dis1[i]=10000;
dis1[S]=0;
pre1[S]=-1;
point tt;
tt.end=S;
tt.time=0;
q1.push(tt);
while(q1.size())
{
int x=q1.top().end;//从堆中取出距离最小的点
q1.pop();
if(vis1[x]==1)
continue;
vis1[x]=1;
for(int i=0;i<line[x].size();i++)
{
int y=line[x][i].end;
int w=line[x][i].time;
if(dis1[y]>(dis1[x]+w))//松弛操作
{
dis1[y]=dis1[x]+w;
point pp;
pp.end=y;
pp.time=dis1[y];
q1.push(pp);
pre1[y]=x;//记录其前一个点
}
}
}
min_path=dis1[E];//不使用商业线的最短距离
for(int i=1;i<=N;i++)
dis2[i]=10000;
dis2[E]=0;
pre2[E]=-1;
tt.end=E;
tt.time=0;
q2.push(tt);
while(q2.size())//第二次Dijkstra,同理
{
int x=q2.top().end;
q2.pop();
if(vis2[x]==1)
continue;
vis2[x]=1;
for(int i=0;i<line[x].size();i++)
{
int y=line[x][i].end;
int w=line[x][i].time;
if(dis2[y]>(dis2[x]+w))
{
dis2[y]=dis2[x]+w;
point pp;
pp.end=y;
pp.time=dis2[y];
q2.push(pp);
pre2[y]=x;
}
}
}
cin>>K;
bool shangye=false;//是否使用了商业线
int st,ed;//商业线的起点和终点
for(int i=0;i<K;i++)
{//对每一条商业线进行枚举
int xx,yy,zz,temp_min;
cin>>xx>>yy>>zz;
if(dis1[xx]+zz+dis2[yy]>dis1[yy]+zz+dis2[xx])
//取两者的最小值
{
temp_min=dis1[yy]+zz+dis2[xx];
if(temp_min<min_path)//若使用商业线的距离小于经济线
{
min_path=temp_min;//更新最短距离
shangye=true;//记录
st=yy,ed=xx;
}
}
else
{
temp_min=dis1[xx]+zz+dis2[yy];
if(temp_min<min_path)
{
min_path=temp_min;
shangye=true;
st=xx,ed=yy;
}
}
}
if(shangye==true)
{//如果使用了商业线
vector<int> te1,te2;
int sst=st;
int eed=ed;
te1.push_back(sst);
while(pre1[sst]!=-1)//将路线分成了两段输出
{
te1.push_back(pre1[sst]);
sst=pre1[sst];
}
te2.push_back(eed);
while(pre2[eed]!=-1)
{
te2.push_back(pre2[eed]);
eed=pre2[eed];
}
for(int i=te1.size()-1;i>=0;i--)
cout<<te1[i]<<" ";
for(int i=0;i<te2.size();i++)
{
cout<<te2[i];
if(i!=te2.size()-1)
cout<<" ";
}
cout<<endl;
cout<<st<<endl;
cout<<min_path<<endl;
}
else
{//未使用商业线
vector<int> te;
int thend=E;
te.push_back(thend);
while(pre1[thend]!=-1)
{
te.push_back(pre1[thend]);
thend=pre1[thend];
}
for(int i=te.size()-1;i>=0;i--)
{
cout<<te[i];
if(i!=0)
cout<<" ";
}
cout<<endl;
cout<<"Ticket Not Used"<<endl;
cout<<min_path<<endl;
}
}
}