最短路多起点多终点(超级源点)

我们先来回忆一下spfa判负环的思想:

就是先把所有的点入队列,然后用一个一个点去枚举边去松弛点与点之间的距离,顺便记录一下被松弛点的最短路径所经过的边数,如果发现一个点的最短路径边数大于等于n(n为点数),那就说明图中存在负环。

那有的同学可能就会有疑问了,为什么一开始要把所有的点都加入队列呢?那是因为图不一定是连通的,也就是说你一开始所选定的那个点可能根本就到不了负环,那我们一定要把所有点都加入队列吗?其实也不完全是,我们只要保证图是联通的不就好了吗?

于是我们就可以建立一个虚拟源点,然后用这个点与其余各个点连一条零边,这不就保证图的连通性了么?先给出这样优化的代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
const int N=1e5+10;
int h[N],d[N],ne[N],e[N],w[N],idx,cnt[N];
bool vis[N];
int n,m;
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
bool spfa()
{
	queue<int> q;
	memset(vis,false,sizeof vis);
	d[0]=-0x3f3f3f3f;//保证超级源点使图中所有点都入队列 
	memset(cnt,0,sizeof cnt);
	q.push(0);
	while(q.size())
	{
		int begin=q.front();
		q.pop();
		vis[begin]=false;
		for(int i=h[begin];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(d[j]>d[begin]+w[i])
			{
				d[j]=d[begin]+w[i];
				cnt[j]=cnt[begin]+1;
				if(cnt[j]>n) return true;
				q.push(j);
				vis[j]=true;
			}
		}
	}
	return false;
}
int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof h);
	int x,y,z;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
	}
	for(int i=1;i<=n;i++) add(0,i,0);
	if(spfa()) puts("Yes");
	else puts("No");
	return 0;
}

题目链接:852. spfa判断负环 - AcWing题库

但需要注意的是,针对本题而言我们加入超级源点的目的是为了使所有的点都通过超级源点来入队列,那么入队列的条件是什么呢?是d[j]>d[begin]+w[i],所以我们就有必要把超级源点一开始的初始值定为负无穷大,以保证图中的点都能够顺利入队。

下面给出一个例子具体体会一下超级源点的使用技巧:

One day , Kiki wants to visit one of her friends. As she is liable to carsickness , she wants to arrive at her friend’s home as soon as possible . Now give you a map of the city’s traffic route, and the stations which are near Kiki’s home so that she can take. You may suppose Kiki can change the bus at any station. Please find out the least time Kiki needs to spend. To make it easy, if the city have n bus stations ,the stations will been expressed as an integer 1,2,3…n.

Input

There are several test cases.
Each case begins with three integers n, m and s,(n<1000,m<20000,1=<s<=n) n stands for the number of bus stations in this city and m stands for the number of directed ways between bus stations .(Maybe there are several ways between two bus stations .) s stands for the bus station that near Kiki’s friend’s home.
Then follow m lines ,each line contains three integers p , q , t (0<t<=1000). means from station p to station q there is a way and it will costs t minutes .
Then a line with an integer w(0<w<n), means the number of stations Kiki can take at the beginning. Then follows w integers stands for these stations.

Output

The output contains one line for each data set : the least time Kiki needs to spend ,if it’s impossible to find such a route ,just output “-1”.

Sample Input

5 8 5
1 2 2
1 5 3
1 3 4
2 4 7
2 5 6
2 3 5
3 5 1
4 5 1
2
2 3
4 3 4
1 2 3
1 3 4
2 3 2
1
1

Sample Output

1
-1

题意就是:琪琪要拜访他的朋友,告诉你他朋友家附近的公交站点,又告诉你一些公交站点之间的路线及花费时间,最后告诉你琪琪一开始可以去的公交站点,问你琪琪到他朋友家最少要花多长时间:

分析:可以想到的是最优路线开始的站点一定是题目中给出的站点之一,所以最容易想到的思路就是从给出的每个从琪琪家可以乘坐的站点都跑一遍最短路,这样取最小值就可以得到最优答案,但显然会超时,那我们就虚拟一个0号公交站点,并与从琪琪家可以乘坐的每个站点之间连一条长度为0的点(长度为0不会影响最终答案),这样我们从0号点跑一次最短路不就可以找到到琪琪朋友家的最优时间了吗

这道题还有一种思路,就是虽然一开始从琪琪家可以乘坐的公交站点有多个,但是琪琪朋友家附近的公交站点却只有一个,那我们就可以反向建边,从琪琪朋友家附近的公交站点跑一次最短路,然后对琪琪一开始可以乘坐的公交站点的最短时间取一个最小值也能得到最优时间。

下面是代码:

//建立超级源点,连接出发站点 
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=5e4+10;
int h[1003],e[N],w[N],ne[N],idx,d[1003];
bool vis[N];
int n,m,s,W;
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void spfa(int x)
{
	memset(vis,false,sizeof vis);
	memset(d,0x3f,sizeof d);
	d[x]=0;
	queue<int> q;
	q.push(x);
	vis[x]=true;
	while(q.size())
	{
		int begin=q.front();
		q.pop();
		vis[begin]=false;
		for(int i=h[begin];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(d[j]>d[begin]+w[i])
			{
				d[j]=d[begin]+w[i];
				if(!vis[j])
				{
					q.push(j);
					vis[j]=true;
				}
			}
		}
	}
}
int main()
{
	while(scanf("%d%d%d",&n,&m,&s)!=EOF)
	{
		memset(h,-1,sizeof h);
		idx=0;//千万不能不初始化
		for(int i=1;i<=m;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z);
		}
		scanf("%d",&W);
		for(int i=1;i<=W;i++)
		{
			int o;
			scanf("%d",&o);
			add(0,o,0);//建立0点与初始公交站的距离为0,从0点出发 
		}
		spfa(0);
		if(d[s]==0x3f3f3f3f) printf("-1\n");
		else printf("%d\n",d[s]);
	}
	return 0;
}


//反向建边,从终点搜起点 
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=5e4+10;
int h[1003],e[N],w[N],ne[N],idx,d[1003];
bool vis[N];
int n,m,s,W;
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void spfa(int x)
{
	memset(vis,false,sizeof vis);
	memset(d,0x3f,sizeof d);
	d[x]=0;
	queue<int> q;
	q.push(x);
	vis[x]=true;
	while(q.size())
	{
		int begin=q.front();
		q.pop();
		vis[begin]=false;
		for(int i=h[begin];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(d[j]>d[begin]+w[i])
			{
				d[j]=d[begin]+w[i];
				if(!vis[j])
				{
					q.push(j);
					vis[j]=true;
				}
			}
		}
	}
}
int main()
{
	while(scanf("%d%d%d",&n,&m,&s)!=EOF)
	{
		memset(h,-1,sizeof h);
		idx=0;//千万不能不初始化
		for(int i=1;i<=m;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			add(y,x,z);//反向建边 
		}
		spfa(s);//从终点搜起点 
		scanf("%d",&W);
		int ans=0x3f3f3f3f;
		for(int i=1;i<=W;i++)
		{
			int o;
			scanf("%d",&o);
			ans=min(ans,d[o]);
		}
		if(ans==0x3f3f3f3f) printf("-1\n");
		else printf("%d\n",ans);
	}
	return 0;
}

这道题是一个多起点,唯一终点的最短路问题,我们可以建立超级源点把所有起点连起来,然后用源点去搜终点,也可以把终点作为起始点,反向建边,反向搜索。

倘若我们遇到了多起点多终点的问题,我们就可以建立超级源点把所有起点连起来,然后用源点去跑最短路,然后遍历每一个终点去找最优解。

下面就给出一道多起点多终点的题目:

题目大意:

德平学长要在今年假期同学家玩,他家附近有若干城市,他只能从这几个城市出发,因为这几个城市有机场。同时,他也只想去中国的几个城市,如北京、上海、台北、拉萨等等。他想快去快回,请你选择耗时最少的一条旅游路线,当然,你只需要计算从出发城市到目的城市的最小耗时即可,无需计算返程。

Input

输入数据有多组,每组的第一行是三个整数T,C和P,表示有T次航班;德平家附近有机场的城市有C个,德平想去的地方有P个;
接着有T行,每行有三个整数a,b,time,表示a,b城市所需的通行时间;(1=<(a,b)<=1000;a,b 之间可能有多个航班,耗时不同) 需要注意的是,若a飞往b有一次耗时为time1的航班,我们认为b飞往a也有一次耗时为time1的航班。
接下来的1行有C个数,表示德平家附近的可出发城市;
接下来的1行有P个数,表示德平想去的城市。
所有城市的数量不会超过1000个。

Output

输出德平能去某个喜欢的城市的最小耗时。

Sample Input

6 2 3
1 3 5
1 4 7
2 8 12
3 8 4
4 9 12
9 10 2
1 2
8 9 10

Sample Output

9

这道题目就是一个多起点多终点的问题,我们的思路就是建立一个超级源点,然后用超级源点向初始地点连权值为0的边,然后跑spfa,最后对多个终点遍历即可,当然也可以在终点也建立一个超级源点,将终点与超级源点连起来,最后两个超级源点之间的最小距离就是答案,下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=5e4+10;
int h[1003],e[N],w[N],ne[N],idx,d[1003];
bool vis[N];
int n,m,s,W;
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void spfa(int x)
{
	memset(vis,false,sizeof vis);
	memset(d,0x3f,sizeof d);
	d[x]=0;
	queue<int> q;
	q.push(x);
	vis[x]=true;
	while(q.size())
	{
		int begin=q.front();
		q.pop();
		vis[begin]=false;
		for(int i=h[begin];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(d[j]>d[begin]+w[i])
			{
				d[j]=d[begin]+w[i];
				if(!vis[j])
				{
					q.push(j);
					vis[j]=true;
				}
			}
		}
	}
}
int main()
{
	int p;
	while(scanf("%d%d%d",&m,&W,&p)!=EOF)
	{
		memset(h,-1,sizeof h);
		idx=0;
		for(int i=1;i<=m;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z);add(y,x,z); 
		}
		for(int i=1;i<=W;i++)
		{
			int o;
			scanf("%d",&o);
			add(0,o,0);//建立超级源点并连边 
		}
		spfa(0);
		int ans=0x3f3f3f3f,v;
		for(int i=1;i<=p;i++)
		{
			scanf("%d",&v);
			ans=min(ans,d[v]);
		}
		printf("%d\n",ans);
	}
	return 0;
}

如果大家还有一些超级源点的应用题的话,欢迎在评论区里面分享!

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值