通过这道题了解二进制分组.
由于我们只需要求两两之间最短路的值而不需要求具体是哪两个点得到的最短路,可以使用二进制分组.
因为如果两个点对答案有贡献,那么这两个点一定在某个二进制位上不同,而 dijkstra 可以方便地求两个集合之间的最短路.
然后注意对于两个方向要分别跑一个 dijkstra.
总时间复杂度为 $O( (n+m) log (n+m) log n)$.
code:
#include <queue> #include <cstdio> #include <cstring> #include <algorithm> #define N 100006 #define M 500009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; const ll inf=1000000000000000; int n,m,K,s,t,tot; int A[N]; struct Edge { int v,c; Edge(int v=0,int c=0):v(v),c(c){} }; vector<Edge>G[N]; struct Dijkstra { ll d[N]; int vis[N]; struct data { int u;ll d; data(int u=0,ll d=0):u(u),d(d){} bool operator<(const data b) const { return d>b.d; } }; priority_queue<data>q; ll solve() { for(int i=0;i<=n+1;++i) { vis[i]=0,d[i]=inf; } d[s]=0; q.push(data(s,0)); while(!q.empty()) { data e=q.top(); q.pop(); int u=e.u; if(vis[u]) { continue; } vis[u]=1; for(int i=0;i<G[u].size();++i) { Edge p=G[u][i]; if(d[p.v]>d[u]+1ll*p.c) { d[p.v]=d[u]+1ll*p.c; q.push(data(p.v,d[p.v])); } } } return d[t]; } }D; void solve() { int x,y,z; scanf("%d%d%d",&n,&m,&K); for(int i=1;i<=m;++i) { scanf("%d%d%d",&x,&y,&z); if(x==y) { continue; } G[x].pb(Edge(y,z)); } for(int i=1;i<=K;++i) scanf("%d",&A[i]); s=0,t=n+1; ll ans=inf; for(int i=0;(1<<i)<=n;++i) { for(int j=1;j<=K;++j) { if((1<<i)&A[j]) { G[s].pb(Edge(A[j],0)); } else { G[A[j]].pb(Edge(t,0)); } } ans=min(ans,D.solve()); for(int j=1;j<=K;++j) { if((1<<i)&A[j]) { G[s].pop_back(); } else { G[A[j]].pop_back(); } } } for(int i=0;(1<<i)<=n;++i) { for(int j=1;j<=K;++j) { if((1<<i)&A[j]) { G[A[j]].pb(Edge(t,0)); } else { G[s].pb(Edge(A[j],0)); } } ans=min(ans,D.solve()); for(int j=1;j<=K;++j) { if((1<<i)&A[j]) { G[A[j]].pop_back(); } else { G[s].pop_back(); } } } printf("%lld ",ans); for(int i=0;i<=n+1;++i) { G[i].clear(); } } int main() { // setIO("input"); int T; scanf("%d",&T); while(T--) { solve(); } return 0; }