最短路专题(week7作业)

一、基础

1.floyd

可参考这个
主要用于:求取任意两点的关系、多源最短路、传递闭包
复杂度O(N^3)
局限:图中可以有负边(不可有负环,负环没有最短路),但是对于单源最短路问题大材小用

主要代码(摘自教学ppt)
在这里插入图片描述

2.dijkstra

详情参考这里

主要用于:求解图中没有负边的单源最短路问题
局限:图中不可有负边
代码:
在这里插入图片描述

3. Bellman-ford及SPFA

Bellman-ford是一种单源最短路解法,图中可以有负边也可以有负环,但因负环没有最短路,所以求解时要把负环及被负环影响的点求出,因为他们没有最短路。

是对每一条边都进行松弛,第i次松弛后,所有经过i条边的最短路都被确定。代码(复杂度:O(NM)):在这里插入图片描述
但是算法中,每一轮都有很多无效的松弛操作,为了避免这种情况,采用队列进行优化,形成SPFA
参考这个
代码:在这里插入图片描述
但同时,对于Bellman-ford和SPFA,如何判断没有到达s的最短路?若图中存在负权环路(环路总权值<0)又当如何?
答:判断不可达:dis[s]=inf;
判断负权环路:最短路经过的边数>=n,或者一些边松弛的次数>=n。特殊的,在SPFA中,若一个点入队的次数>=n,则存在负环。

修改后的Bellman-ford代码:在这里插入图片描述
修改后的SPFA代码:在这里插入图片描述
当然,这样只是找到的负环,还要将图中被负环影响的点标出,

二、应用

1.floyd求传递闭包:A - TT 的魔法猫

题目:

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

这一天,TT 正在专心致志地玩《猫和老鼠》游戏,然而比赛还没开始,聪明的魔法猫便告诉了 TT 比赛的最终结果。TT 非常诧异,不仅诧异于他的小猫咪居然会说话,更诧异于这可爱的小不点为何有如此魔力?

魔法猫告诉 TT,它其实拥有一张游戏胜负表,上面有 N 个人以及 M 个胜负关系,每个胜负关系为 A B,表示 A 能胜过 B,且胜负关系具有传递性。即 A 胜过 B,B 胜过 C,则 A 也能胜过 C。

TT 不相信他的小猫咪什么比赛都能预测,因此他想知道有多少对选手的胜负无法预先得知,你能帮帮他吗?

Input
第一行给出数据组数。

每组数据第一行给出 N 和 M(N , M <= 500)。

接下来 M 行,每行给出 A B,表示 A 可以胜过 B。
output
对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。
sample

3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
0
0
4

解题思路

用dis二维数组表示x到y的距离。初始时将图中任意两点之间的距离都设为无穷大,自己与自己的dis距离设为零。

用弗洛伊德算法计算传递闭包。注意在计算传递闭包的时候,注意剪枝。如果起始点i到中转点k的距离是无穷大,那么就直接跳过。因为无论中转点到达之后的哪一个点,松弛都不会成功。

最终遍历dis,如果两点之间互相到达的距离都为正无穷大,那么说明俩点不联通。计算点的数量并输出。

c++代码

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxd=1e4;
const int maxn=600;
int ans=0,count1=0;
int dis[maxn][maxn]={maxd};//dis[x][y]表示x到y的距离
void init()
{
	ans=0,count1=0;
	for(int i=0;i<maxn;i++)
		for(int j=0;j<maxn;j++)
		{
			if(i==j)dis[i][j]=dis[j][i]=0;//自己与自己的距离为0 
			else dis[i][j]=maxd;
		}
}


int main()
{
	int t;
	cin>>t;
	int n,m,a,b;
	while(t--)
	{
		scanf("%d %d",&n,&m);
		init();
		while(m--)
		{
			cin>>a>>b;
			dis[a][b]=1;
		}
		for(int k=1;k<=n;k++)
			for(int i=1;i<=n;i++)
			{
				if(dis[i][k]==maxd)//剪枝
					continue; 
				for(int j=1;j<=n;j++)
				{
					dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

				}
			}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				if(dis[i][j]==maxd&&dis[j][i]==maxd)count1++;
			
		cout<<count1/2<<endl;
	}
	return 0;
}

2.dijkstra:B - TT 的旅行日记

题目:

众所周知,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 前往喵星机场花费的总时间。

本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
sample

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

解题思路

如果忽略题目中的商业线,本题是单源最短路问题。

若考虑商业线并且只能走一条商业线的话,可以遍历每一条商业线,对其中的一条商业线来说,记录它的起点和终点,并且找到这两点到达TT家里和喵星机场的最短路径。最终比较所有的路径找到题目要求的路径就好。

本题用链式前向星存储只有经济线的图。从起点遍历找到每一点到达起点的最短路径,再从终点遍历找到每一点到达终点的最短路径。用vector记录每一条商业线。遍历每一条商业线,找到路径最短的那一条。

本题注意输出格式。同时也要注意求解最短路径时的相关数据置零问题。

c++代码

#include<iostream>
#include<stdio.h>
#include<queue>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;

const int maxn=600;
const int inf=1e8;
//链式前向星存储图 
struct edge
{
	int u,v,w,next;
	edge(int _u,int _v,int _w)
	{
		u=_u;
		v=_v;
		w=_w;
	}
	edge(){}
}edges[5000];

int head[maxn],tot=0;
vector<edge> thisV;
int path[maxn];
priority_queue<pair<int,int> > q;//默认为大顶堆,降序 
//priority_queue<pair<int,int>,vector<int>,greater<int> >q;//升序 
//https://blog.csdn.net/weixin_36888577/article/details/79937886 
int dis[maxn];
int vis[maxn];


void init()
{
	tot=0;
	for(int i=0;i<maxn;i++)
		head[i]=-1;
	thisV.clear();
}
void addEdge(int u,int v,int w)
{
	edges[tot].u=u;
	edges[tot].v=v;
	edges[tot].w=w;
	edges[tot].next=head[u];
	head[u]=tot;
	tot++;
}

//最短路单源无负边 
void initVis()
{
	for(int i=0;i<maxn;i++)
		dis[i]=inf,vis[i]=0,path[i]=-1;
	while(q.size())q.pop();
	
}

void dijkstra(int s)
{
	initVis();
	dis[s]=0;
	q.push(make_pair(dis[s],s));
	//https://blog.csdn.net/yockie/article/details/6980692
	while(q.size())
	{
		int a=q.top().second;
		q.pop();
		if(vis[a]==1)continue;
		vis[a]=1;
		for(int i=head[a];i!=-1;i=edges[i].next)
		{
			if(dis[edges[i].v]>dis[a]+edges[i].w)
			{
				dis[edges[i].v]=dis[a]+edges[i].w;
				q.push(make_pair(-dis[edges[i].v],edges[i].v));
				path[edges[i].v]=a;
			}
		}
	}
	
}

int main()
{
	int n,s,e,c=0;
	while(~scanf("%d %d %d",&n,&s,&e))
	{
		init();
		int m,x,y,z,k;
		scanf("%d",&m);//
		while(m--)
		{
			scanf("%d %d %d",&x,&y,&z);
			addEdge(x,y,z);
			addEdge(y,x,z);
		}
		scanf("%d",&k);
		while(k--)
		{
			scanf("%d %d %d",&x,&y,&z);
			//addEdge(x,y,z);
			//addEdge(y,x,z);
			edge e(x,y,z);
			thisV.push_back(e);
		}
		dijkstra(s);
		int *tmp=new int[n+1];
		int *path1=new int[n+1];
		for(int i=1;i<=n;i++)tmp[i]=dis[i],path1[i]=path[i];
		dijkstra(e);
		int mint=tmp[e];int flag=0,coutu=-1,coutv=-1;
		for(int i=0;i<thisV.size();i++)
		{
			int ss=thisV[i].u,ee=thisV[i].v,ww=thisV[i].w;
			int len1=tmp[ss]+ww+dis[ee];
			if(mint>len1)
			{
				mint=len1;
				coutu=ss,coutv=ee;
				flag=1;
			}
			int len2=tmp[ee]+ww+dis[ss];
			if(mint>len2)
			{
				mint=len2;
				coutu=ee,coutv=ss;
				flag=1;
			}
		}
		
		if(c>0)cout<<endl;
		//使用栈正序输出 
		stack<int> thisS;
		if(flag==0)//没有使用商业线
		{
			int t=e;
			while(path1[t]!=-1)
			{
				thisS.push(path1[t]);
				t=path1[t];
			}	
			while(!thisS.empty())
			{
				cout<<thisS.top()<<' ';
				thisS.pop();
			}
			cout<<e<<endl<<"Ticket Not Used"<<endl<<tmp[e]<<endl;

		}
		else if(flag==1)
		{
			int t=coutu;
			while(path1[t]!=-1)
			{
				thisS.push(path1[t]);
				t=path1[t];
			}
			while(!thisS.empty())
			{
				cout<<thisS.top()<<' ';
				thisS.pop();
			}
			cout<<coutu<<' '<<coutv;
			t=coutv;
			while(t!=e)
			{
				cout<<' '<<path[t];
				t=path[t];
			}
			cout<<endl<<coutu<<endl<<mint<<endl;
			
		}
		
		c++;
	}
	return 0;
}

3.SPFA:C - TT 的美梦

题目

这一晚,TT 做了个美梦!

在梦中,TT 的愿望成真了,他成为了喵星的统领!喵星上有 N 个商业城市,编号 1 ~ N,其中 1 号城市是 TT 所在的城市,即首都。

喵星上共有 M 条有向道路供商业城市相互往来。但是随着喵星商业的日渐繁荣,有些道路变得非常拥挤。正在 TT 为之苦恼之时,他的魔法小猫咪提出了一个解决方案!TT 欣然接受并针对该方案颁布了一项新的政策。

具体政策如下:对每一个商业城市标记一个正整数,表示其繁荣程度,当每一只喵沿道路从一个商业城市走到另一个商业城市时,TT 都会收取它们(目的地繁荣程度 - 出发地繁荣程度)^ 3 的税。

TT 打算测试一下这项政策是否合理,因此他想知道从首都出发,走到其他城市至少要交多少的税,如果总金额小于 3 或者无法到达请悄咪咪地打出 ‘?’。

input
第一行输入 T,表明共有 T 组数据。(1 <= T <= 50)

对于每一组数据,第一行输入 N,表示点的个数。(1 <= N <= 200)

第二行输入 N 个整数,表示 1 ~ N 点的权值 a[i]。(0 <= a[i] <= 20)

第三行输入 M,表示有向道路的条数。(0 <= M <= 100000)

接下来 M 行,每行有两个整数 A B,表示存在一条 A 到 B 的有向道路。

接下来给出一个整数 Q,表示询问个数。(0 <= Q <= 100000)

每一次询问给出一个 P,表示求 1 号点到 P 号点的最少税费。

output
每个询问输出一行,如果不可达或税费小于 3 则输出 ‘?’。

sample

2
5
6 7 8 9 10
6
1 2
2 3
3 4
1 5
5 4
4 5
2
4
5
10
1 2 4 4 5 6 7 8 9 10
10
1 2
2 3
3 1
1 4
4 5
5 6
6 7
7 8
8 9
9 10
2
3 10
Case 1:
3
4
Case 2:
?
?

解题思路

注意到本题图中可能含有负权环路又是解决单源最短路径问题,所以使用SPFA解决。

用链式前向星存储该有向图,dis数组存储起点到该点的距离,初始化距离为正无穷大。

当进行SPFA时,需要判断是否有负环。在本题中,用cnt存储原点到该点的最短路径长度,当路径长度>=n时,说明存在负环。负环中的点及被负环影响到的点都是没有最短路径的,所以要将其标记出来。用dfs从该店遍历,将遍历到的点的dis值设为无穷大加一。注意,遍历到的点可能是已经存在在队列中的点,也可能是算法没来得及松弛的点,所以SPFA的while循环中,要判断点的dis值是否为最大值加一,如果是,跳过。同理,for循环中判断是否加入队列时也要判断dis值是否为无穷大加一,如果是,则不能加入队列。

最终按照题目要求,如果是没有最短路或距离小于三的点都输出问号。

c++代码

#include<iostream>
#include<stdio.h>
#include<queue>
#include<math.h>
#include<algorithm>
using namespace std;
const int maxn=300;
const int maxm=100010;
const int inf=1e8;
//链式前向星存储图 
struct edge
{
	int v,w,next;
}edges[maxm];

queue<int> thisQ;
int head[maxn],tot=0;
int dis[maxn];
int inq[maxn];
int pre[maxn];
int cnt[maxn];//记录到达u点的最短路经过的边数 
int n;


void init()
{
	tot=0;
	for(int i=0;i<maxn;i++)
		head[i]=-1;
	
}
void addEdge(int u,int v,int w)
{
	edges[tot].v=v;
	edges[tot].w=w;
	edges[tot].next=head[u];
	head[u]=tot;
	tot++;
}

//最短路 
void initVis()
{
	for(int i=0;i<maxn;i++)
		dis[i]=inf,cnt[i]=0,inq[i]=0,pre[i]=0; 
	
	
}

void dfs(int t)
{
	dis[t]=inf+1;
	for(int i=head[t];i!=-1;i=edges[i].next)
		if(dis[edges[i].v]!=inf+1)
			dfs(edges[i].v);
 } 

void spfa(int s)
{
	initVis();
	dis[s]=0;
	inq[s]=1;
	thisQ.push(s);
	while(!thisQ.empty())
	{
		int u=thisQ.front();thisQ.pop();
		inq[u]=0;
		if(dis[u]==inf+1)continue;//负环通路中的点跳过 
		for(int i=head[u];i!=-1;i=edges[i].next)
		{
			int v=edges[i].v;
			if(dis[v]>dis[u]+edges[i].w)
			{
				//松弛成功 
				dis[v]=dis[u]+edges[i].w;
				cnt[v]=cnt[u]+1;
				if(cnt[v]>=n)//存在负环
				{
					dfs(v);//将被负环影响的点标记,这里使其dis值无穷大 +1
				} 
				
				pre[v]=u;
				if(!inq[v]&&dis[v]!=inf+1)//如果到达的点不在队列中 
				{
					thisQ.push(v);
					inq[v]=1;
				}
			}
			
		}
	}
	
}


int main()
{
	int t,m,q;
	cin>>t;
	int l=1;
	while(t--)
	{
		init();//重置图为n个独立的点 
		scanf("%d",&n);
		int *a=new int[n+1];
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		scanf("%d",&m);
		int aa,bb;
		while(m--)
		{
			scanf("%d %d",&aa,&bb);
			addEdge(aa,bb,pow(a[bb]-a[aa],3));
		}
		spfa(1);
		scanf("%d",&q);
		
		printf("Case %d:\n",l);
		l++;
		while(q--)
		{
			int numb;
			scanf("%d",&numb);
			if(dis[numb]==inf||dis[numb]<3||dis[numb]==inf+1)
				{
					printf("?\n");
				}
			else printf("%d\n",dis[numb]);
		}
		
	 } 
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值