Week7 B——TT的旅行日记

本文介绍了如何帮助TT找到从起点到喵星机场最快的一条路线,其中包括考虑是否使用魔法猫变出的商业线车票。通过Dijkstra算法分析最短路径,并提供了详细的解题思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TT的旅行日记

众所周知,TT 有一只魔法猫。

今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。 TT 从家里出发,准备乘坐猫猫快线前往喵星机场。猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。当然啦,商业线要比经济线贵,TT 平常只能坐经济线,但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,不然就要误机了!

输入

输入包含多组数据。每组数据第一行为 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 行是商业线路段的描述,格式同经济线。

所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。

输出

对于每组数据,输出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 算法

该算法主要用于解决图中没有负边的单源最短路问题
复杂度𝑂((𝑛+𝑚)𝑙og𝑛)
设置 s 为源点,𝑑is[𝑎] 表示源点 s 到点 a 的最短距离, 初始化 𝑑is[s]=0,𝑑is[i]=inf(无穷大), 将 s 加入最小堆
每次从堆中取出一个点 x,遍历 x 的所有邻接边 (𝑥 𝑦 𝑤),比较𝑑is[𝑦] 与𝑑is[𝑥]+𝑤的大小 (松弛操作)
若dis[y]>dis[x]+w,则松弛成功,更新dis[y] = 𝑑is[𝑥]+𝑤,将y加入最小堆。
重复以上操作直到最小堆为空。

解题思路

题目给定了起点与终点,而且要求商业线最多乘坐一次,因此可以枚举每一条商业线(u,v),计算起点到u的最短路以及v到 终点的最短路再加上该商业线所花费的时间。

为了记录路径,定义数组pre1,pre2记录经过节点的父节点。
使用两次Dijkstra算法,以起点为源点求单源最短路,得到 dis1 数组 ,记录路径pre1,再以终点为源点求单源最短路,得到 dis2 数组,记录路径pre2。
为了记录路径,在Dijkstra算法松弛操作中,若松弛成功就记录b的上一个节点为a。

枚举商业线(u, v, w),取 min{dis1[u]+dis2[v]+w, dis1[v]+dis2[u]+w},最终再与不走商业线的答案取min

输出路径
这个题出错最多的是输出路径部分,定义一个数组a存放路径。因为pre1只能从end开始访问到start,pre2只能从start开始访问到end,因此不能直接输出。
如果没有使用商业线,把终点放入a,从终点开始把父节点依次放入a中,然后倒序输出a即可。
如果使用了商业线(x, y),则路径应该为start->x->y->end。先把y放入a,在沿着pre2从y访问到end放入,为了方便统一输出,使用swap把y->end反向记录为end->y。沿pre1从x访问到start。最后倒序输出。

代码实现

#include<stdio.h>
#include<queue>
#include<utility>
#include<vector> 
#define N 550
using namespace std;

struct edge
{
	int v,net;
	int w;
};

int n,st,ed;
int m;
int head[N],vis[N];
int *dis1,*dis2;
int *pre1,*pre2;
edge e[5000];
priority_queue< pair<int,int> > q;
int index;

void add(int u, int v, int w)
{
		e[index].v = v;
    	e[index].w = w;
    	e[index].net = head[u];
    	head[u] = index;
    	index++;
	
}

void dij(int start, int end, int dis[], int pre[])
{
	while(!q.empty()) q.pop();
	
	for(int i=0; i<=n; i++) pre[i] = 0,vis[i]=0, dis[i] = 1000000;
	
	q.push(make_pair(0,start));
	dis[start] = 0;
	while(!q.empty())
	{
		int a=q.top().second;
		q.pop();

    	if(vis[a] == 1) continue;
		//存储路径 
		
		vis[a] = 1;
		
		for(int i=head[a]; i!=-1; i=e[i].net)
		{
			int b = e[i].v;
			int w = e[i].w;
			if(dis[b] > dis[a]+w)
			{
				dis[b] = dis[a]+w;
				pre[b] = a;
				q.push(make_pair((-1)*dis[b], b));
			}
		}
	}
}

int main()
{
	int cishu=0; 
	while(scanf("%d%d%d", &n,&st,&ed) != EOF)
	{
		for(int i=1; i<=n; i++) head[i] = -1;
		index=0;
		
		scanf("%d",&m);
		for(int i=0; i<m; i++)
		{
			int u,v,w;
			scanf("%d%d%d",&u, &v, &w);
			add(u,v,w);
			add(v,u,w);
		}
		
		dis1 = new int[n+2];
		dis2 = new int[n+2];
		pre1 = new int[n+2];
		pre2 = new int[n+2];
		dij(st,ed,dis1,pre1);
		dij(ed,st,dis2,pre2);
		int i = dis1[ed];
		int x=0,y=0;
		
		scanf("%d",&m);
		for(int j=0; j<m; j++)
		{
			int u,v,w;
			scanf("%d%d%d",&u, &v, &w);
			if(dis1[u]+dis2[v]+w<i)
			{
				i=dis1[u]+dis2[v]+w;
				x=u;
				y=v;
			}
			if(dis2[u]+dis1[v]+w<i)
			{
				i=dis2[u]+dis1[v]+w;
				x=v;
				y=u;
			}
		}
		
		
		if(cishu>0) printf("\n");
		cishu++;
		int a[250];
		int ia=0;
		int temp=0;
		if(x == 0)
		{
			temp = ed;
			while(temp!=0)
			{
				a[ia] = temp;
				ia++;
				if(temp == st) break;
				temp = pre1[temp];
			}
			ia--;
			for(; ia>0; ia--) printf("%d ",a[ia]);
			printf("%d\n",a[0]);
			
			printf("Ticket Not Used\n");
		 } 
		else 
		{
			temp=y;
			while(temp)
			{
				a[++ia]=temp;
				if(temp==ed) break;
				temp=pre2[temp];
			}
			for(int t=1;t<=ia/2;t++)
		    	swap(a[t],a[ia-t+1]);
			temp=x;
			while(temp)
			{
				a[++ia]=temp;
				if(temp==st) break;
				temp=pre1[temp];
			}
			for(int t=ia;t>1;t--) printf("%d ",a[t]);
			printf("%d\n",a[1]);

		
			printf("%d\n",x);
		}
		
		printf("%d\n",i);
	}
 } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值