[GX/GZOI2019]旅行者(dijkstra)
二进制分组SB做法没意思还难写还可能会被卡常其实是我不会写。用一种比较优秀的O(Tnlogn)做法,只需要做2次dijkstra。对原图做一次、对反图做一次,然后记录每个点的最短路是从k个源点中的哪个转移过来的。然后枚举每条边,若两边转移过来的源点不同,则用d1+w[i]+d2来更新答案即可。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e5+7,M=5e5+7; struct node{int u;ll d;}; bool operator<(node a,node b){return a.d>b.d;} int n,m,k,tot,a[N],vis[N],hd[N],v[M],nxt[M],w[M],st[M],ed[M],len[M],c[2][N]; ll ans,d[2][N]; void adde(int x,int y,int z){v[++tot]=y,nxt[tot]=hd[x],w[tot]=z,hd[x]=tot;} void dijkstra(ll*d,int*c) { priority_queue<node>q; for(int i=1;i<=n;i++)d[i]=1e18,vis[i]=0; for(int i=1;i<=k;i++)d[a[i]]=0,c[a[i]]=a[i],q.push((node){a[i],0}); while(!q.empty()) { int u=q.top().u;q.pop(); if(vis[u])continue; vis[u]=1; for(int i=hd[u];i;i=nxt[i]) if(d[v[i]]>d[u]+w[i])d[v[i]]=d[u]+w[i],c[v[i]]=c[u],q.push((node){v[i],d[v[i]]}); } } int main() { int T;scanf("%d",&T); while(T--) { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=n;i++)hd[i]=0;tot=0; for(int i=1,x,y,z;i<=m;i++) { scanf("%d%d%d",&x,&y,&z); if(x==y)i--,m--; else adde(x,y,z),st[i]=x,ed[i]=y,len[i]=z; } for(int i=1;i<=k;i++)scanf("%d",&a[i]); dijkstra(d[0],c[0]); for(int i=1;i<=n;i++)hd[i]=0;tot=0; for(int i=1;i<=m;i++)adde(ed[i],st[i],len[i]); dijkstra(d[1],c[1]); ans=1e18; for(int i=1;i<=m;i++) if(c[0][st[i]]&&c[1][ed[i]]&&c[0][st[i]]!=c[1][ed[i]]) ans=min(ans,d[0][st[i]]+d[1][ed[i]]+len[i]); printf("%lld\n",ans); } }