P5304 [GXOI/GZOI2019] 旅行者

题目大意

给定 nnn1≤n≤1051\le n \le 10^51n105) 个顶点 mmm1≤m≤5×1051\le m \le 5×10^51m5×105)条单向边,每条边都有一定的权值 www。然后,再给定 kkk1≤k≤n1\le k \le n1kn)个特殊点。求出 kkk 个顶点之间两两最短路的最小值。

分析

暴力做法

kkk 个顶点,以每个顶点作为起点跑一遍最短路,然后枚举。时间复杂度为 O(kmlog⁡n+n2)O(km\log n + n^2)O(kmlogn+n2)

优化

在面对有多个起点的最短路问题时,比较适用的方法是,建立一个虚拟源起点用边权为 0 的边去连接它们。这样,就变为了一个只有一个起点的问题。
但由于,在此问题中,kkk 个顶点不仅是起点,也是终点。所以,到达它们的最短路长度一定为 0。只有次短路,才是起点通过一些边到达终点的有效路径。不过,这也可能会出现起点与终点相同的情况。因此,我们要完成的任务是求解出的是从 kkk 个顶点出发,到达其它顶点,且起点与终点不同的次短路长度。
设从 kkk 个起点出发到达顶点 GGG 的起点与终点不同的次短路方案为:
{a1,a2,...,as,G}\{a_1,a_2,...,a_s,G\}{a1,a2,...,as,G}
能够保证的是 a2,a3,...,asa_2,a_3,...,a_sa2,a3,...,as 它们不可能是 kkk 个顶点的其中一个顶点。当 as=ia_s = ias=iiiiGGG 的邻接点),方案变为{a1,a2,...,as−1,i,G}\{a_1,a_2,...,a_{s-1},i,G\}{a1,a2,...,as1,i,G}
方案的属性:

  1. 目的地。子方案的目的地变为 iii
  2. 路径长度。原方案的路径长度为子方案的路径长度 + w(i,G)w(i,G)w(i,G)。由于,原方案求解的是次短路。因此,子方案的路径长度求解的是最短路或次短路。
  3. 起点。不能是顶点 GGG

所以,子方案反映的问题为:起点不能是顶点 GGG ,目的地是 iii 的最短路和次短路。

但是,对于顶点 iii 来说,它可以是很多顶点的邻接点。因此,这将产生:起点不能是顶点 G1G_1G1G2G_2G2G3G_3G3、…、GkG_kGk ,目的地是 iii 的最短路和次短路的问题。即使该求解能够完成,时间复杂度也至少达到 O(n2)O(n^2)O(n2)

思维点

由于,到达 GGG 的最短路径一定为 0,已经被确定。那么实际上,我们只需要求出起点不能是顶点 G1G_1G1G2G_2G2G3G_3G3、…、GkG_kGk ,目的地是 iii 的最短路问题。

而上述 kkk 个问题,实际上只需要两个问题就能完成求解。

  1. 目的地是 iii 的最短路问题。(假设它的起点是 G1G1G1,那么它与其它 “起点是 G2,G3,...,GkG_2,G_3,...,G_kG2,G3,...,Gk” 的结果是相同的)
  2. 目的地是 iii 但起点与最短路不同的的次短路径问题。

示例程序

#include <bits/stdc++.h>
using namespace std;
struct node {
	int to;
	long long val;
	int flag;
	bool operator< (const node& n) const {
		return val>n.val;
	}
};
const int N=1e5+10;
vector<node> G[N];
long long dis[N][2];
int vis[N][2],col[N],who[N][2];
int main() {
	int t;
	cin>>t;
	while (t--) {
		int n,m,k;
		scanf("%d%d%d",&n,&m,&k);
		for (int i=1; i<=n; i++) {
			G[i].clear();
			dis[i][0]=dis[i][1]=1e18;
			vis[i][0]=vis[i][1]=0;
			who[i][0]=who[i][1]=0;
			col[i]=0;
		}
		for (int i=1; i<=m; i++) {
			int x,y,z; scanf("%d%d%d",&x,&y,&z);
			G[x].push_back({y,z,0});
		}
		priority_queue<node> PQ;
		for (int i=1; i<=k; i++) {
			int x; cin>>x;
			col[x]=1;
			dis[x][0]=0;
			who[x][0]=x;
			PQ.push({x,0,0});
		}
		long long res=-1;
		while (!PQ.empty()) {
			node p=PQ.top();
			PQ.pop();
			if (col[p.to]&&who[p.to][p.flag]!=p.to) {
				res=p.val;
				break;
			}
			if (vis[p.to][p.flag]) continue;
			vis[p.to][p.flag]=1;
			int s=who[p.to][p.flag];
			for (int i=0; i<G[p.to].size(); i++) {
				int v=G[p.to][i].to;
				long long val=p.val+G[p.to][i].val;
				if (val<=dis[v][0]) {
					if (who[v][0]==s) {
						dis[v][0]=val;
					} else {
						dis[v][1]=dis[v][0];
						who[v][1]=who[v][0];
						dis[v][0]=val;
						who[v][0]=s;
						PQ.push({v,dis[v][1],1});
					}
					PQ.push({v,val,0});
					
				} else if (val<dis[v][1]) {
					if (who[v][0]!=s) {
						dis[v][1]=val;
						who[v][1]=s;
						PQ.push({v,val,1});
					}
				}
			}
		}
		cout<<res<<"\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值