[GXOI/GZOI2019]旅行者

122 篇文章 0 订阅

题目

传送门 to luogu

思路

思路一:暴力

题解区就有这样的一篇题解:暴力艹标程。

然后就加了一组数据把它卡掉了……

思路二:多源点

把所有的点一口气塞进去。

先在正向图上跑一边;再在反向图上跑一边。

然后枚举一条边作为必经之路。

战斗结束了。

当然有一个要点:存储一下是从谁到达了自己。也就是说,它究竟是距离哪个特殊点最近。这条边两边的 f r o m from from不同才可更新。显然,最短的点对之间的路径上一定存在这样的一条边。

思路三:奇技淫巧

谁脑洞这么大,想到这种方法

延续上面的多源点方法。

最暴力的方法,之所以会被卡掉,完全是因为它搜索了太多不可能更优的情况。

那么,多源点就可以很好的解决问题——这些点一起出发,一旦搜到一个终点就停止。

问题就是怎么选择这些多源点?毕竟,如果你不使用双向最短路,两个源点之间的路径就不会被考虑!

答案是,枚举二进制中的某一位,按照这一位上的取值分类。也就是说,如果 x x x的第 i i i位是 1 1 1,就作为源点。

为什么它是对的呢?因为 x , y x,y x,y至少有一位是不同的,那么就会被考虑到。

简直是奥妙重重!

代码

我写的是思路二版本的。

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(x%10+'0');
}

const int MaxN = 200005;
const long long infty = (1ll<<60)-1;
int n, m, k;
namespace Graph{
	struct Edge{
		int to, nxt, val;
		Edge(int T=0,int N=0,int V=0){
			to = T, nxt = N, val = V;
		}
	} edge[MaxN*10];
	int cntEdge, head[MaxN<<1];
	void clear(int N){
		for(int i=1; i<=N; ++i)
			head[i] = -1;
		cntEdge = 0;
	}
	void addEdge(int from,int to,int val){
		edge[cntEdge] = Edge(to,head[from],val);
		head[from] = cntEdge ++;
	}
} using namespace Graph;

void input(){
	n = readint(), m = readint(), k = readint();
	clear(n<<1);
	for(int i=1,u,v,w; i<=m; ++i){
		u = readint(), v = readint(), w = readint();
		addEdge(u,v,w); addEdge(v+n,u+n,w);
	} /* 编号(n,2n]是反图 */
}

long long dis[MaxN<<1]; int from[MaxN<<1];
struct Status{
	int x; long long val;
	bool operator<(const Status &that)const{
		return val > that.val;
	}
	Status(int X=0,long long V=0):x(X),val(V){ }
};
priority_queue<Status> pq;
void dijkstra(){
	Status t;
	while(not pq.empty()){
		t = pq.top(), pq.pop();
		if(dis[t.x] < t.val) continue;
		for(int i=head[t.x]; ~i; i=edge[i].nxt)
			if(dis[edge[i].to] > t.val+edge[i].val){
				dis[edge[i].to] = t.val+edge[i].val;
				from[edge[i].to] = from[t.x];
				pq.push(Status(edge[i].to,dis[edge[i].to]));
			}
	}
}

void solve(){
	for(int i=1; i<=(n<<1); ++i)
		dis[i] = infty, from[i] = (i-1)%n+1;
	for(int i; k; --k){
		i = readint();
		dis[i] = dis[i+n] = 0;
		pq.push(Status(i,0));
		pq.push(Status(i+n,0));
	}
	dijkstra();
	long long ans = infty;
	for(int x=1; x<=n; ++x)
		for(int i=head[x]; ~i; i=edge[i].nxt)
			if(from[edge[i].to+n] != from[x])
				ans = min(ans,dis[x]+dis[edge[i].to+n]+edge[i].val);
	writeint(ans), putchar('\n');
}

int main(){
	for(int T=readint(); T; --T)
		input(), solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值