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

点此看题面

  • 给定一张(n)个点(m)条边的有向图和(k)个关键点,求关键点两两之间最短路的最小值。
  • (nle10^5,mle5 imes10^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
",ans);//二进制分组
	}return 0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5304.html