(L3-007)天梯地图(dijkstra+路径输出)

题目链接:PTA | 程序设计类实验辅助教学平台

输入样例1:

10 15
0 1 0 1 1
8 0 0 1 1
4 8 1 1 1
5 4 0 2 3
5 9 1 1 4
0 6 0 1 1
7 3 1 1 2
8 3 1 1 2
2 5 0 2 2
2 1 1 1 1
1 5 0 1 3
1 4 0 1 1
9 7 1 1 3
3 1 0 2 5
6 3 1 2 1
5 3

输出样例1:

Time = 6: 5 => 4 => 8 => 3
Distance = 3: 5 => 1 => 3

输入样例2:

7 9
0 4 1 1 1
1 6 1 3 1
2 6 1 1 1
2 5 1 2 2
3 0 0 1 1
3 1 1 3 1
3 2 1 2 1
4 5 0 2 2
6 5 1 2 1
3 5

输出样例2:

Time = 3; Distance = 4: 3 => 2 => 5

 分析:先说点题外话,这道题确实比较容易出错,而且还没给出m的数据范围,属实有点坑了。

下面来进行分析:
相信大家读完题意肯定就知道这道题目考察的是dijkstra最短路了,一个是按照时间,另一个是按照距离,先来说一下这个按照距离来求的最短路,题目中说若存在多条最短距离相同的路线就输出途径节点最少的那条,这就要求我们在按照距离求最短路的时候需要加一个数组来记录当前路径的途径节点数,这个很容易想到,加上这个之后这一个问题基本上就没有什么问题了,详情可以参照代码,我主要想说的是按照时间求最短距离的步骤,题目中说若最快到达路线不唯一,则输出几条最快路线中最短的那条,这个地方一定要好好思考,我一开始的想法是先跑距离最短路,这样在跑时间最短路的时候还能用到之前跑距离最短路更新的距离d数组来求最优解,也就是说当时间相同时,我优先选择在之前距离最短路中求出的d数组中的较小值,但这样的想法是错误的,希望大家务必要注意这个地方(就这个地方我debug了整整一天),我来说一下错误原因,题目中要求的意思是在时间最少的几条路径中选取距离最短的路径,而不是说所有路径中距离最短的,因为所有路径中距离最短的路径不一定满足时间最短这一条件,这个地方非常容易出错,明白了这一点,大家就肯定知道应该怎么做了,就是在求最短时间的路径中顺便更新最短距离,也就是说时间为第一关键词而距离为第二关键词。

最后说一下这个题的数据,我看到了n是500,但是题目中没给边数,就以为m也不会很大,就把存边的数组开到了1e4,结果出现了数组越界的情况,最后开到1e6才ac,这里给大家的一个建议就是,当题目中有些变量没有给出明确范围时,在不超过内存限制的情况下应该尽可能开大点,防止越界

下面是代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
typedef pair<int,int>PII;
const int N=1e6+10;
int h[2][N],ne[2][N],e[2][N],w[2][N];
int d[2][N],path[2][N],st[2][N],idx;
bool vis[N];
int cnt[2][N];//记录路线中节点个数 
void add(int id,int x,int y,int z)
{
	e[id][idx]=y;
	w[id][idx]=z;
	ne[id][idx]=h[id][x];
	h[id][x]=idx++;
}
void dijkstra(int id,int x)
{
	memset(d,0x3f,sizeof d);
	memset(cnt,0x3f,sizeof cnt);
	memset(vis,false,sizeof vis); 
	
	d[0][x]=d[1][x]=0;//无论是求最短时间还是求最短距离都需要用到最短距离 
	cnt[id][x]=1;//求最短距离的时候会用到节点数 
	priority_queue<PII,vector<PII>,greater<PII> >q;
	q.push({0,x});
	while(!q.empty())
	{
		int begin=q.top().second;
		q.pop();
		if(vis[begin]) continue;
		vis[begin]=true;
		for(int i=h[id][begin];i!=-1;i=ne[id][i])
		{
			int j=e[id][i];
			if(d[id][j]>d[id][begin]+w[id][i]) 
			{
				if(id==0)  d[1][j]=min(d[1][j],d[1][begin]+w[1][i]);//当求时间的时候必须要重新计算距离数组 
				cnt[id][j]=cnt[id][begin]+1;
				d[id][j]=d[id][begin]+w[id][i];
				q.push({d[id][j],j});
				path[id][j]=begin;//记录路径 
			}
			else if(d[id][j]==d[id][begin]+w[id][i]) 
			{
				if(((id==0)&&(d[1][j]>d[1][begin]+w[1][i]))||((id==1)&&(cnt[1][j]>cnt[1][begin]+1)))
				{
					cnt[id][j]=cnt[id][begin]+1;
					d[1][j]=d[1][begin]+w[1][i];
					q.push({d[id][j],j});
					path[id][j]=begin;//记录路径
				}
			}
		}
	}
}
int main()
{
	memset(h,-1,sizeof h);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v,single,length,t;
		scanf("%d%d%d%d%d",&u,&v,&single,&length,&t);
		u++;v++;//让下标从1开始 
		if(single)
		{
			add(0,u,v,t);
			idx--;//保证相同的边对应的idx是相同的 
			add(1,u,v,length);
		}
		else
		{
			add(0,u,v,t);
			idx--;
			add(1,u,v,length);
			add(0,v,u,t);
			idx--;
			add(1,v,u,length);
		}
	}
	int b,end;
	cin>>b>>end;
	b++;end++;
	
	//存储时间路径 
	int pend=end;
	dijkstra(0,b);
	int tt1=0;
	st[0][++tt1]=pend;
	while(path[0][pend])
	{
		st[0][++tt1]=path[0][pend];
		pend=path[0][pend];
	}
	int anst=d[0][end];
	//存储距离路径 
	pend=end;
	dijkstra(1,b);
	int tt2=0;
	st[1][++tt2]=pend;
	while(path[1][pend])
	{
		st[1][++tt2]=path[1][pend];
		pend=path[1][pend];
	}
	int anslength=d[1][end];
	
	bool flag=true;//判断两种情况路径是否相同 
	if(tt1!=tt2) flag=false;
	else
	{
		for(int i=1;i<=tt1;i++)
			if(st[0][i]!=st[1][i])
			{
				flag=false;
				break;
			}
	}
	if(flag)
	{
		printf("Time = %d; ",anst);
		printf("Distance = %d: %d",d[1][st[1][1]],st[1][tt2]-1);
		for(int i=tt2-1;i>=1;i--)
			printf(" => %d",st[1][i]-1);
	}
	else
	{
		printf("Time = %d: %d",anst,st[0][tt1]-1);
		for(int i=tt1-1;i>=1;i--)
			printf(" => %d",st[0][i]-1);
		puts("");
		printf("Distance = %d: %d",anslength,st[1][tt2]-1);
		for(int i=tt2-1;i>=1;i--)
			printf(" => %d",st[1][i]-1);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值