7-4 超级玛丽
题目描述:
假定有n个城堡,编号为1至n,有的城堡之间有道路直接相连,有的城堡之间没有道路直接相连。马里奥现在准备从一个城堡出发前往另一个城堡,它有一个魔法棒,可以瞬时通过一条道路,即以0时间通过这条道路,但魔法棒最多只能用一次。马里奥想以最短的时间到达目的地,请编写程序为马里奥选定一条路线以及在什么地方使用魔法棒。假定所有道路为双向,保证从起点肯定能到达目的地。
输入格式:
输入第一行为4个整数n、s、t、m,分别表示城堡数(编号为1至n,n不超过10000),马里奥所在的起点s和想去的终点t,城堡间的道路数目。接下来m行,每行为3个正整数a、b、c,表示城堡a和城堡b之间有一条道路直接相连,通过该道路需要c分钟。
输出格式:
输出为空格间隔的2个整数,第1个整数为马里奥从起点到目的地所需的最短时间;第2个整数表示使用魔法棒的地点,即城堡编号,若有多个可能的地点,则输出编号最小者。
输入样例:
4 1 4 4
1 2 9
2 4 1
1 3 3
3 4 5
输出样例:
1 1
代码实现:
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef pair<int,int>PII;
const int N=2e4+5,INF=0x3f3f3f3f;
//h数组存储邻接表的头,ne数组存储下一个结点,e数组用于存储当前结点下标,w数组用于存储当前边的长度
int h[N],ne[2*N],e[2*N],w[2*N],idx;
//dist1数组起点到任意其它点的最短距离
//dist2数组终点到任意其它点的最短距离
int dist1[N],dist2[N],vis[N];
//邻接表建边操作
void add(int x,int y,int z){
e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;
}
void dijkstra(int x,int dist[]){
//初始化所有结点都未被访问
memset(vis,0,sizeof vis);
//初始化x到其他所有点的距离为最大值
memset(dist,0x3f,sizeof 4*N);
//优先队列优化
priority_queue<PII,vector<PII>,greater<PII> >q;
//第一个参数记录当前点到x点的距离
//第二个参数记录当前点的下标
q.push({0,x});
//将x到它自己的距离初始化为0
dist[x]=0;
while(q.size()){
//取出当前结点到x点的距离
int dis=q.top().first;
//取出当前点的下标
int s=q.top().second;
//出队列
q.pop();
//如果当前点已经访问过了,直接返回,相当于剪枝
if(vis[s])continue;
//将当前结点标记为已访问
vis[s]=1;
//遍历与当前结点s直接相连的所有结点
for(int i=h[s];~i;i=ne[i]){
//取出直接相连的结点的下标
int j=e[i];
//如果存在比x到j还短的路径
if(dist[j]>dis+w[i]){
//更新x到j的最短路径
dist[j]=dis+w[i];
//走当前点
//即将当前点和当前点到x的最短距离入队列
q.push({dist[j],j});
}
}
}
}
int main(){
//邻接表头初始化
memset(h,-1,sizeof h);
int n,s,t,m;
//n表示城堡的个数,s表示起点,t表示终点,m表示道路数量
cin>>n>>s>>t>>m;
while(m--){
int x,y,z;
cin>>x>>y>>z;
//建立双向边
add(x,y,z);
add(y,x,z);
}
//先跑一遍起点到任意其它点的最短距离
dijkstra(s,dist1);
//再跑一遍终点到任意其它点的最短距离
dijkstra(t,dist2);
//res用于记录起点到终点且使用一次魔法棒的最短路径
//idx1用于记录使用魔法棒所在的最小城堡编号
int res=INF,idx1=-1;
//遍历起点的中间结束点
for(int i=1;i<=n;i++){
//遍历与i结点直接相连的结点
for(int j=h[i];~j;j=ne[j]){
//k表示与结点i直接相连的结点编号
int k=e[j];
//dist1[i]相当与起点到结点i的最短路径
//diat2[k]相当于终点到结点k的最短路径
//而i和k是直接相连的,所以dist1[i]+dist2[k]相当与起点到终点的距离减去中间i结点到k结点的距离
if(dist1[i]+dist2[k]<res){
//如果存在起点到终点的更小值,则更新起点到终点的最短距离
res=dist1[i]+dist2[k];
//同时更新使用魔法棒所在的最小城堡编号(切记要保证取i和k两者最小值)
idx1=min(i,k);
}else if(dist1[i]+dist2[k]==res){
//如果存在另一条最短距离相同的路径,则只需要更新使用魔法棒所在的最小城堡编号
idx1=min(idx1,min(i,k));
}
}
}
//输出最短距离和使用魔法棒所在的最小城堡编号
cout<<res<<" "<<idx1<<endl;
return 0;
}