「GXOI / GZOI 2019」旅行者

传送门


problem

J 国有 n n n 座城市,这些城市之间通过 m m m 条单向道路相连,已知每条道路的长度。

一次,居住在 J 国的 Rainbow 邀请 Vani 来作客。不过,作为一名资深的旅行者,Vani 只对 J 国的 k k k 座历史悠久、自然风景独特的城市感兴趣。

请求出 Vani 感兴趣的城市之间「两两最短路」的最小值(即在他感兴趣的城市中,最近的一对的最短距离)。

数据范围: n ≤ 1 0 5 n≤10^5 n105 m ≤ 5 × 1 0 5 m≤5\times 10^5 m5×105 2 ≤ k ≤ n 2\le k\le n 2kn


solution

这道题其实不难。

我们跑两次 dijkstra,第一次从所有关键点出发,每个点记录两个值: d i s 1 [ i ] dis_1[i] dis1[i] i i i 到最近的关键点的距离, p r e 1 [ i ] pre_1[i] pre1[i] 为最近关键点的编号。第二次终点是关键点(相当于是在反图上以关键点为起点跑),同样记两个值: d i s 2 [ i ] dis_2[i] dis2[i] p r e 2 [ i ] pre_2[i] pre2[i]

然后我们可以枚举边 ( u , v ) (u,v) (u,v),如果 p r e 1 [ u ] ≠ p r e 2 [ v ] pre_1[u]\ne pre_2[v] pre1[u]=pre2[v],则以 d i s 1 [ u ] + w ( u , v ) + d i s 2 [ v ] dis_1[u]+w(u,v)+dis_2[v] dis1[u]+w(u,v)+dis2[v] 更新答案。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)


code

#include<bits/stdc++.h>
#define N 100005
#define ll long long
using namespace std;
typedef pair<int,ll>Pair;
int n,m,k,t;
int a[N],tag[N],vis[N],pre1[N],pre2[N];
vector<Pair>G[N],rG[N];
ll dis1[N],dis2[N];
priority_queue<Pair>Q;
void dijkstra(ll *dis,int *pre){
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(ll)*(n+1));
	for(int i=1;i<=k;++i)
		dis[a[i]]=0,pre[a[i]]=a[i],Q.push(Pair(0,a[i]));
	while(!Q.empty()){
		int x=Q.top().second;Q.pop();
		if(vis[x])  continue;vis[x]=1;
		for(Pair &now:G[x]){
			int to=now.first;
			if(dis[to]>dis[x]+now.second){
				dis[to]=dis[x]+now.second;
				pre[to]=pre[x],Q.push(Pair(-dis[to],to));
			}
		}
	}
}
int main(){
	int T,u,v,w;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=m;++i){
			scanf("%d%d%d",&u,&v,&w);
			G[u].push_back(Pair(v,w)),rG[v].push_back(Pair(u,w));
		}
		for(int i=1;i<=k;++i)  scanf("%d",&a[i]),tag[a[i]]=1;
		dijkstra(dis1,pre1);
		for(int i=1;i<=n;++i)  G[i]=rG[i];
		dijkstra(dis2,pre2);
		ll ans=1e18;
		for(int i=1;i<=n;++i)
			for(Pair &x:G[i])
				if(pre1[x.first]&&pre2[i]&&pre1[x.first]!=pre2[i])
					ans=min(ans,dis1[x.first]+dis2[i]+x.second);
		printf("%lld\n",ans);
		for(int i=1;i<=n;++i)  tag[i]=0,G[i].clear(),rG[i].clear(),pre1[i]=pre2[i]=0;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值