[GXOI/GZOI2019]旅行者【k点最短路最小值】

题目描述:

n个点m条有向边,给定k个点,求两两最短路的最小值。
n<=100000,m<=500000.

题目分析:

很棒的一道题。

首先是比较正规的思路,按编号二进制分组跑最短路,一部分作为起点,另一部分作为终点。由于是有向图,二进制分组要分两遍。

然后是比较容易想到但是很玄学的优化思路,就是在枚举每个点作起点跑最短路时只拓展在已经求出的答案范围内的点,并且在Dijkstra确定了另一个点时直接return。不容易被hack。

另外是尝试k个点一起做Dijkstra,求到每个点的最短和次短路,并保证最短和次短的起点不是同一个,那么最后k个点中每个点的次短路的最小值就是答案(最短路为0)。

最后是牛逼的正解思路,考虑最后答案的最短路,其中一定存在一条边 ( u , v , w ) (u,v,w) (u,v,w),使得起点到 u u u为最短, v v v到终点为最短。只需要正反做两遍Dijkstra,记录每次达到最小值的对应的是k个点中的哪个点(记为 c o l [ ] col[] col[]),枚举每条边,如果 c o l [ u ] ≠ c o l [ v ] col[u]\neq col[v] col[u]=col[v],则将 d i s [ 0 ] [ u ] + w + d i s [ 1 ] [ v ] dis[0][u]+w+dis[1][v] dis[0][u]+w+dis[1][v]与答案取min。
首先答案统计的都是col[u]!=col[v]的点,所以取得的值一定大于等于答案。然后由于k个点的初值为0,所以一定取得到最短路径上的某条边,即能够计入答案。
如果为无向图的话,只需要k个点一起做一遍Dijkstra,如上所述计算答案即可。

Code(考试时写的二进制分组):

#include<bits/stdc++.h>
#define maxn 100005
#define maxm 500005
#define LL long long
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int T,n,m,k,a[maxn];
int fir[maxn],nxt[maxm],to[maxm],w[maxm],tot;
inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
LL dis[maxn];
typedef pair<LL,int> pii;
priority_queue<pii,vector<pii>,greater<pii> >q;
void Dijkstra(int p,bool flg){
	memset(dis,0x3f,(n+1)<<3);
	for(int i=1;i<=k;i++) if((i>>p&1)==flg) q.push(pii(dis[a[i]]=0,a[i]));
	while(!q.empty()){
		int u=q.top().second;LL d=q.top().first;q.pop();
		if(dis[u]!=d) continue;
		for(int i=fir[u],v;i;i=nxt[i])
			if(dis[v=to[i]]>dis[u]+w[i]) dis[v]=dis[u]+w[i],q.push(pii(dis[v],v));
	}
}
int main()
{
	freopen("tourist.in","r",stdin);
	freopen("tourist.out","w",stdout);
	read(T);int x,y,z;
	while(T--){
		memset(fir,0,sizeof fir),tot=0;
		read(n),read(m),read(k);
		while(m--) read(x),read(y),read(z),line(x,y,z);
		for(int i=1;i<=k;i++) read(a[i]);
		LL ans=1ll<<60;
		for(int i=log2(k)+1;i>=0;i--){
			Dijkstra(i,1);
			for(int j=1;j<=k;j++) if(!(j>>i&1)) ans=min(ans,dis[a[j]]);
			Dijkstra(i,0);
			for(int j=1;j<=k;j++) if(j>>i&1) ans=min(ans,dis[a[j]]);
		}
		printf("%lld\n",ans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值