1. 程式人生 > 其它 >[GXOI/GZOI2019]旅行者

[GXOI/GZOI2019]旅行者

[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