深入虎穴 题解

发布时间 2023-07-27 17:18:16作者: linbaicheng2022

1.题目大意

有一个复杂的虎穴包括了 \(N\) 个节点(编号为 \(0\)\(N-1\) )和 \(M\) 条无向的通道

其中通道 \(i(0 \leq i < M)\) 连接两个节点 \(R[i][0],R[i][1]\),长为 \(L[i]\)

有K个出口节点,分别为\(P_0\) , \(P_1\)\(P_{k-1}\)

小强从\(0\)号节点出发,他想尽快到达一个出口节点。

而洞穴中有一只会瞬间移动的老虎。

小强每次到达一个节点,老虎就会瞬间移动到与这个节点相邻的某个通道里使得小强无法使用这个通道。

不过,小强一旦选择了另一个没有被封锁的通道,老虎就不会在小强到达这个通道的目的地前改变位置。

老虎非常聪明,它总能让小强离开洞穴所要消耗的时间最长。

而小强也非常聪明,他能够计算出最快的逃生方案。

为了让题目更加严谨,

我们规定小强的逃生方案是如下的形式:

对于每个节点\(X\),给它的所有相邻的边\(<X,Y>\)指定一个权值\(f(X,Y)\),注意,\(f(X,Y)\) 不等于 \(f(Y,X)\)

在一个节点,小强选择未被封锁的权值最小的通道逃生,直到到达出口。所有的 \(f(X,Y)\) 两两不同。

你要计算小强的最快逃离时间 \(T\) 并输出。

2.思路

由题可知,在小强每一步路中,老虎都会抢占接下来到终点的最短路径,所以小强只能选择次短路走。也就是说,实际上我们要求的就是一条从入口到出口的次短路。

但是题中给出了 \(k\) 个出口节点,直接求解非常麻烦,所以我们不妨倒着走一遍,在堆中先加入所有出口节点,再用 \(dijkstra\) 算法求出它们到入口 \(0\) 号节点的次短路距离。

我们用 \(dis[i][0]\) 表示从 \(i\) 到出口的最短路距离, \(dis[i][1]\) 表示从 \(i\) 到出口的次短距离,算法结束后,结果就是 \(dis[0][1]\)

3.代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
// #define int long long

using namespace std;

#define N 100010
#define M 2000010
#define For(i,j,k) for(int i=j;i<=k;i++)
#define IOS ios::sync_with_stdio(),cin.tie(),cout.tie()
typedef pair <int, int> PII;

int n, m, k, x[N];
int h[M], w[M], e[M], ne[M], dis[N][2], st[N], idx = 0;

void add (int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void dijkstra () {
	priority_queue <PII, vector <PII>, greater <PII> > p;
	For (i, 1, k) p.push ({0, x[i]}); //在堆中加入k个出口节点
	
	while (!p.empty ()) {
		auto t = p.top ();
		p.pop (); //取出队头,.second指指向点的编号,.first指边权
		int ver = t.second, dit = t.first;
		
		if (st[ver]) {
			continue;
		} //如果点已经用过,退出
		
		st[ver] = true;
		for (int i = h[ver]; i != -1; i = ne[i]) {
			int j = e[i]; //j指当前节点所联通的子节点
			if (dis[j][1] > dis[ver][1] + w[i]) { //如果前i-1个点的次短路加上L(i-1,i)大于当前i点次短路,更新它
				if (dis[j][0] > dis[ver][1] + w[i]) { //如果当前点最短路也能被更新,就一起更新掉
					dis[j][1] = dis[j][0]; //将次短路变为之前最短路
					dis[j][0] = dis[ver][1] + w[i]; //将原最短路更新为新加入最短的边
				} else { //如果最短路不变,次短路能更新
					dis[j][1] = dis[ver][1] + w[i]; //更新次短路。
				}

				p.push ({dis[j][1], j}); //将这个子节点加入堆,边权为这个点到出口的次短路(用于排序)
			}
		}
	}
}

int main () {
    IOS;
	memset (h, -1, sizeof h);
	memset (dis, 0x3f, sizeof dis); //将最短路与次短路初值赋为无穷大
	cin >> n >> m >> k;

	For (i, 1, m) {
		int a, b, w;
		cin >> a >> b >> w;
		add (a, b, w);
		add (b, a, w); //建双向边
	}
	
	For (i, 1, k) {
		cin >> x[i];
		dis[x[i]][0] = dis[x[i]][1] = 0; //出口到自己的最短路与次短路距离都是0
	}
	
	dijkstra ();
	cout << dis[0][1] << endl; //0到出口的次短路距离即为答案
	return 0;
}