P5304 [GXOI/GZOI2019] 旅行者 题解
P5304 [GXOI/GZOI2019] 旅行者
Description
给你一个 \(n\) 个点,\(m\) 条边的有向连通图,给出 \(k\) 个点的编号,让你求出这些点中距离最近的两点之间距离。
\(n\le 10^5,m\le 5\times 10^5\)。
Solution
这题是一个十分经典的 trick —— 二进制分组。
大概的思路类似于有源汇网络流,即建立一个超级源点和超级汇点,跑最短路。具体来说,把 \(k\) 个结点随机分到两个集合 \(s\) 和 \(t\) 中,用一个 bool 数组来记录这 \(k\) 个点,如果编号为 \(i\) 的点在 \(s\) 中记为 \(0\),在 \(t\) 中记为 \(1\)。
由于这个图是有向图,所以先从超级源点向所有 \(s\) 中的点连边,从 \(t\) 中的所有点向 \(t\) 连边跑 \(\log n\) 次最短路,再反过来跑 \(\log n\) 次最短路,最后所有最短路的最小值即为答案。
时间复杂度 \(O(Tn\log n\log k)\),可以通过。
#include<bits/stdc++.h>
#define int long long
using namespace std;
long long T,n,m,k,a[500005],ans;
priority_queue<pair<int,int>>q;
struct graph{
long long tot,head[500005],id[500005],dis[500005];
struct node{
int from,to,w,nxt;
}e[500005];
inline void init(){
tot=0;
memset(head,0,sizeof(head));
return;
}
inline void add(int u,int v,int w){
e[++tot].from=u;
e[tot].to=v;
e[tot].w=w;
e[tot].nxt=head[u];
head[u]=tot;
return;
}
inline void dijkstra(){
for(int i=1;i<=500000;i++){
dis[i]=LONG_LONG_MAX;
id[i]=0;
}
for(int i=1;i<=k;i++){
dis[a[i]]=0;
id[a[i]]=a[i];
q.push(make_pair(0,a[i]));
}
while(!q.empty()){
int u=q.top().second;
int d=-q.top().first;
q.pop();
if(d==dis[u]){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w;
if(dis[v]>d+w){
dis[v]=d+w;
id[v]=id[u];
q.push(make_pair(-dis[v],v));
}
}
}
}
return;
}
}G[2];
signed main(){
cin>>T;
while(T--){
cin>>n>>m>>k;
G[0].init();
G[1].init();
for(int i=1;i<=m;i++){
int u,v,w;
cin>>u>>v>>w;
if(u^v){
G[0].add(u,v,w);
G[1].add(v,u,w);
}
}
for(int i=1;i<=k;i++){
cin>>a[i];
}
G[0].dijkstra();
G[1].dijkstra();
ans=LONG_LONG_MAX;
for(int u=1;u<=n;u++){
for(int i=G[0].head[u];i;i=G[0].e[i].nxt){
int v=G[0].e[i].to;
int w=G[0].e[i].w;
if(G[0].id[u]&&G[1].id[v]&&G[0].id[u]^G[1].id[v]){
ans=min(ans,G[0].dis[u]+G[1].dis[v]+w);
}
}
}
cout<<ans<<endl;
}
return 0;
}

浙公网安备 33010602011771号