[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

posted @ 2021-08-22 16:32  归游  阅读(48)  评论(0编辑  收藏  举报