Luogu P5304 [GXOI/GZOI2019]旅行者|最短路
阿新 • • 發佈:2020-11-27
題目大意:有一個\(n\)個點,\(m\)條單向邊的圖,有\(k\)個特殊點,求\(k\)個特殊點間的最短路的最小值。
\(n\le 10^5,m\le 5\times 10^5\)
題目思路:
這個資料大小,用\(k\)次單源最短路的方法是不可行的。這裡需要一個船新的方法:二進位制分組。
方法是:每次將編號中某位二進位制位不同點的分為兩組,一組連源點,一組連匯點,邊權均為\(0\),以源點為起點跑一次最短路,得出源點到匯點的最短路,再反著做一次(因為對於某個點對\((x,y)\)和\((y,x)\)。它們的最短路可能不同)。每一位均操作一次後,得到的結果中的最小值即為答案。這樣,我們只需要做約\(log_2 k\)
我們來證明一下解法的正確性:
對於所有的點對,每兩個點必然有至少一位二進位制位不同。也就是說,至少有一次分組中,兩個點會被分到不同組。
因此,上文解法相當於每個點對都做了次最短路,更準確的說,是一堆點對同時做一次最短路。
上程式碼
#include<bits/stdc++.h> #define S n+1 #define T n+2 #define N 100100 #define M 700100 using namespace std; int cc,to[M],net[M],fr[N],l[M],f[N],h[M],ha[M],p[N];bool vis[N]; int g,n,m,k,t,u,v,len; void addedge(int u,int v,int len) { cc++; if (u==v) return ; to[cc]=v;net[cc]=fr[u];fr[u]=cc;l[cc]=len; } void add(int x,int xx) { g++; h[g]=x;ha[g]=xx; int fa=g/2,so=g; while (h[fa]>h[so]&&fa) { swap(h[fa],h[so]); swap(ha[fa],ha[so]); so=fa;fa=fa/2; } } int del() { int ans=ha[1]; h[1]=h[g];ha[1]=ha[g];g--; int fa=1,so=2; if (h[so]>h[so+1]&&so+1<=g) so++; while (h[fa]>h[so]&&so<=g) { swap(h[fa],h[so]); swap(ha[fa],ha[so]); fa=so;so*=2; if (h[so]>h[so+1]&&so+1<=g) so++; } return ans; } void dij() { for (int i=1;i<=n+2;i++) f[i]=2147483647,vis[i]=false; f[S]=0;add(f[S],S); while (g) { int x=del(); if (vis[x]) continue; for (int i=fr[x];i;i=net[i]) { if (f[to[i]]>f[x]+l[i]) { f[to[i]]=f[x]+l[i]; add(f[to[i]],to[i]); } } } } int main() { cin>>t; for (int tt=1;tt<=t;tt++) { cin>>n>>m>>k; cc=0;int ans=2147483647; for (int i=1;i<=n;i++) fr[i]=0; for (int i=1;i<=m;i++) { cin>>u>>v>>len; addedge(u,v,len); } for (int i=1;i<=k;i++) cin>>p[i]; for (int i=1;i<=k;i=i<<1) { cc=m; for (int j=1;j<=k;j++) { if (i&j) addedge(S,p[j],0);else addedge(p[j],T,0);//二進位制分組 } dij(); ans=min(ans,f[T]); for (int j=1;j<=k;j++)//復原 { if (fr[p[j]]>m) fr[p[j]]=net[fr[p[j]]]; } fr[S]=0; cc=m; for (int j=1;j<=k;j++)//反著做一次 { if (!(i&j)) addedge(S,p[j],0);else addedge(p[j],T,0); } dij(); ans=min(ans,f[T]); for (int j=1;j<=k;j++) { if (fr[p[j]]>m) fr[p[j]]=net[fr[p[j]]]; } fr[S]=0; } cout<<ans<<endl; } return 0; }
當然,還有另外的解法。該解法是更優的。似乎錘了std?
考慮邊\((u,v)\),經過這條邊的,特殊點的最短路的最小值是離\(u\)最近特殊點的最短路+\((u,v)\)長度+離\(v\)最近的特殊點的最短路。
那麼,正反做兩遍dij(從源點開始)即可。
沒寫程式碼