[GXOI/GZOI2019]旅行者
阿新 • • 發佈:2021-08-22
[GXOI/GZOI2019]旅行者
簡化題意
給一個n個點,m條邊的有向圖,找k個點之間的最小距離\((n<=100000 ,m<=500000)\)
時間複雜度:\(O(Tnlogn)\)
有個時間複雜度為\(O(Tnlog_{2}^{2}n)\)的解法
(其實時間允許可以用floyed)
出題人:想得美
其實有個很easy的想法
我們可以預處理出
\(dis1[i]\),\(dis2[i]\)
\(dis1[i]\)表示給定圖點i到最近特殊點的距離
\(dis2[i]\)表示給定圖反圖點i到最近特殊點的距離
(反圖就原圖反向建邊)
然後考慮每條邊\(u,v,w\)
則兩個特殊點的最小距離 則可能用\(dis1[u]+dis2[v]+w\)
但需要注意的是,\(u和v所到的特殊點不能為一個點,且保證都能達到特殊點\)
這個怎麼處理了
可以利用最短路的性質,假如,特殊點到最近的特殊點就是它本身,那麼到特殊點的最近的點則可以由它傳遞(俗稱:染色)
這時我們用求最短路的演算法,以特殊點為源點入隊
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> using namespace std; int n,m,k; const long long inf=0x3f3f3f3f3f3f3f; const int maxn=1e5+10; const int maxm=5e5+10; int head[maxm<<2]; int p[maxn]; int cr1[maxn],cr2[maxn]; bool vl[maxn]; long long dis1[maxn];//原圖,每個點到關鍵點的最短距離 long long dis2[maxn];//反圖,每個點到關鍵點的最短距離 int cnt=0; struct node{ int v,u,next,w; }e[maxm]; int ut[maxm],vt[maxm],wt[maxm]; void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt; } void dj(long long *dis,int *cr){ for(int i=1;i<=n;++i) dis[i]=inf,vl[i]=0; priority_queue<pair<int,int> >q; for(int i=1;i<=k;++i){ dis[p[i]]=0;//特殊點到特殊點的最短距離為0 cr[p[i]]=p[i];//特殊點染色(記錄到這個點最近的點) q.push(make_pair(0,p[i]));//入隊特殊點 //不同於一般單源最短路徑,源點為特殊點,我們的目的是找距特殊點最近的點,同時也要算出距離 } while(!q.empty()){ int u=q.top().second;q.pop(); if(vl[u]) continue; vl[u]=1; for(int i=head[u];i;i=e[i].next){ int v=e[i].v,w=e[i].w; if(dis[v]>dis[u]+w){ dis[v]=dis[u]+w; cr[v]=cr[u];//因為是最短路 ,所以u能到達的最近的特殊點,也為v所達特殊點 q.push(make_pair(-dis[v],v)) ; } } } } int main(){ int t; ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>t; while(t--){ cin>>n>>m>>k; cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=m;++i){ int u,v,w; cin>>u>>v>>w; if(u!=v)add(u,v,w); ut[i]=u,vt[i]=v;wt[i]=w; } for(int i=1;i<=k;++i) cin>>p[i]; dj(dis1,cr1); memset(head,0,sizeof(head)); cnt=0; for(int i=1;i<=m;++i) if(ut[i]!=vt[i])add(vt[i],ut[i],wt[i]);//反向建圖 dj(dis2,cr2); long long ans=inf; for(int i=1;i<=m;++i){ int u=ut[i],v=vt[i],w=wt[i]; if(cr1[u] && cr2[v] && cr1[u]!=cr2[v])//u和v能到達特殊點且兩個點能達到的特殊點不同 ans=min(ans,dis1[u]+dis2[v]+w); } cout<<ans<<endl; } }
總結
- 本來是k個特殊點很多,暴力列舉則我們需要列舉\(k(k+1)/2\)次,但我們處理出每個點到k個點的最短距離,然後列舉邊,相當於避免了直接列舉
- 染色的思想在求聯通時也常用,這裡運用在最短路上,也是神來之筆
- 建立反圖的思想也很常用
ZFY AK IOI