题目描述:
n个点m条有向边,给定k个点,求两两最短路的最小值。
n<=100000,m<=500000.
题目分析:
很棒的一道题。
首先是比较正规的思路,按编号二进制分组跑最短路,一部分作为起点,另一部分作为终点。由于是有向图,二进制分组要分两遍。
然后是比较容易想到但是很玄学的优化思路,就是在枚举每个点作起点跑最短路时只拓展在已经求出的答案范围内的点,并且在Dijkstra确定了另一个点时直接return。不容易被hack。
另外是尝试k个点一起做Dijkstra,求到每个点的最短和次短路,并保证最短和次短的起点不是同一个,那么最后k个点中每个点的次短路的最小值就是答案(最短路为0)。
最后是牛逼的正解思路,考虑最后答案的最短路,其中一定存在一条边
(
u
,
v
,
w
)
(u,v,w)
(u,v,w),使得起点到
u
u
u为最短,
v
v
v到终点为最短。只需要正反做两遍Dijkstra,记录每次达到最小值的对应的是k个点中的哪个点(记为
c
o
l
[
]
col[]
col[]),枚举每条边,如果
c
o
l
[
u
]
≠
c
o
l
[
v
]
col[u]\neq col[v]
col[u]=col[v],则将
d
i
s
[
0
]
[
u
]
+
w
+
d
i
s
[
1
]
[
v
]
dis[0][u]+w+dis[1][v]
dis[0][u]+w+dis[1][v]与答案取min。
首先答案统计的都是col[u]!=col[v]的点,所以取得的值一定大于等于答案。然后由于k个点的初值为0,所以一定取得到最短路径上的某条边,即能够计入答案。
如果为无向图的话,只需要k个点一起做一遍Dijkstra,如上所述计算答案即可。
Code(考试时写的二进制分组):
#include<bits/stdc++.h>
#define maxn 100005
#define maxm 500005
#define LL long long
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
char c;while(!isdigit(c=getc()));
for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int T,n,m,k,a[maxn];
int fir[maxn],nxt[maxm],to[maxm],w[maxm],tot;
inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
LL dis[maxn];
typedef pair<LL,int> pii;
priority_queue<pii,vector<pii>,greater<pii> >q;
void Dijkstra(int p,bool flg){
memset(dis,0x3f,(n+1)<<3);
for(int i=1;i<=k;i++) if((i>>p&1)==flg) q.push(pii(dis[a[i]]=0,a[i]));
while(!q.empty()){
int u=q.top().second;LL d=q.top().first;q.pop();
if(dis[u]!=d) continue;
for(int i=fir[u],v;i;i=nxt[i])
if(dis[v=to[i]]>dis[u]+w[i]) dis[v]=dis[u]+w[i],q.push(pii(dis[v],v));
}
}
int main()
{
freopen("tourist.in","r",stdin);
freopen("tourist.out","w",stdout);
read(T);int x,y,z;
while(T--){
memset(fir,0,sizeof fir),tot=0;
read(n),read(m),read(k);
while(m--) read(x),read(y),read(z),line(x,y,z);
for(int i=1;i<=k;i++) read(a[i]);
LL ans=1ll<<60;
for(int i=log2(k)+1;i>=0;i--){
Dijkstra(i,1);
for(int j=1;j<=k;j++) if(!(j>>i&1)) ans=min(ans,dis[a[j]]);
Dijkstra(i,0);
for(int j=1;j<=k;j++) if(j>>i&1) ans=min(ans,dis[a[j]]);
}
printf("%lld\n",ans);
}
}