把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5304】[GXOI/ZOI2019] 旅行者(二进制分类+最短路)

点此看题面

  • 给定一张\(n\)个点\(m\)条边的有向图和\(k\)个关键点,求关键点两两之间最短路的最小值。
  • \(n\le10^5,m\le5\times10^5\)

二进制分类

一个早就知道的技巧,但一直没有真正写过。

考虑我们枚举二进制下每一位,把这一位为\(0\)的分成一组,这一位为\(1\)的分成一组。

分别从这两组出发跑多源最短路(显然也可以\(Dijkstra\)解决),求出到另一组的最短路最小值。

由于两个不同的点二进制下至少存在一位不同,也就必然在枚举到某一位的时候被分到不同的两组中,所以正确性有了保证。

代码:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 500000
#define LN 17
#define LL long long
#define Pr pair<LL,int>
#define mp make_pair
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,m,k,p[N+5],ee,lnk[N+5];struct edge {int to,nxt,v;}e[M+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
LL ans,dis[N+5];int vis[N+5];priority_queue<Pr,vector<Pr>,greater<Pr> > q;I void Dij(CI w,CI g)//多源最短路
{
	RI i,k;for(i=1;i<=n;++i) vis[i]=0,p[i]&&(i>>w&1)==g?(q.push(mp(dis[i]=0,i)),0):dis[i]=1e18;//从w位为g的关键点出发
	W(!q.empty()) if(k=q.top().second,q.pop(),!vis[k]) for(vis[k]=1,//Dijkstra模板
		i=lnk[k];i;i=e[i].nxt) dis[k]+e[i].v<dis[e[i].to]&&(q.push(mp(dis[e[i].to]=dis[k]+e[i].v,e[i].to)),0);
	for(i=1;i<=n;++i) p[i]&&(i>>w&1)^g&&(ans=min(ans,dis[i]));//到w位为g^1的关键点的最短路
}
int main()
{
	RI Tt,i,x,y,z;read(Tt);W(Tt--)
	{
		for(read(n,m,k),ans=1e18,ee=0,i=1;i<=n;++i) p[i]=lnk[i]=0;for(i=1;i<=m;++i) read(x,y,z),add(x,y,z);
		for(i=1;i<=k;++i) read(x),p[x]=1;for(i=0;i<=LN;++i) Dij(i,0),Dij(i,1);printf("%lld\n",ans);//二进制分组
	}return 0;
}
posted @ 2021-03-26 11:52  TheLostWeak  阅读(71)  评论(0编辑  收藏  举报