Description
给定一个有 $n$ 个节点 $m$ 条边的有向图,一个顶点集 $S$。
你需要选择两个顶点 $u,v$($u e v,uin S,vin S$)并连接这两个顶点(允许 $u,v$ 之间已经有连边),求连接后从顶点 $1$ 到顶点 $n$ 最短路的最大值。
注意,该操作仅能进行一次。
保证给定的图联通。
Solution
我们可以预处理出 $1 o u$ 的最短路记为 $dis_{1 u}$,$u o n$ 的最短路记为 $dis_{2 u}$。
这个可以通过正反各 BFS 一遍得到。
接下来介绍两种不同的解法。
Solution1
对于 $u$,必须经过它 的最短路长度显然是 $dis_{1 u} + dis_{2 u}$。
现在,我们在 $u, v$ 之间添加了一条边。
那么,设想 $dis_{1 u}$ 的求解过程,它是可以从任何一个和 $u$ 相邻的点转移过来的,这些转移点中显然不包含 $v$。那么现在无非就是多了一种从 $v$ 转移的情况,也就是说,$dis_{1 u} gets min(dis_{1 u}, dis_{1, v} + 1)$。
如果原来 $dis_{1 u} le dis_{1, v} + 1$,那么这个转移就没意义了,所以只考虑有意义的情况。显然,$dis_{2 u}$ 是不变的,所以,新的最短路长度就是
$$ dis_{1 v} + 1 + dis_{2 u} $$
算出缩短的长度:
$Delta = (dis_{1 u} + dis_{2 u}) - (dis_{1 v} + 1 + dis_{2 u})$
$quad = dis_{1 u} - dis_{1 v} - 1$
想要让 $Delta$ 尽可能小,显然,我们选择的 $v$ 应当是满足 $dis_{1 u} - dis_{1 v}$ 最小的。而别忘了这里的条件是 $dis_{1 v} + 1 le dis_{1 u}$,也就是说,$dis_{1 v} < dis_{1 u}$。所以按照 $dis_1$ 对 $a$ 排序,边就一定是建在 $a_{i -1}$ 和 $a_i$ 之间的啦!
有人会问,$dis_{1 a_i} = dis_{1 a_{i -1}}$,不就不满足了吗?
没错,但是答案只和 $dis_1$ 的值有关,和 $a_i$ 无关,即便这里出现了一个无意义的建边,但是 $a_{i-1}$ 和它前面建的边,不就相当于 $a_i$ 和 $a_{i-1}$ 前面建的边吗?
时间复杂度 $mathcal O(n log n)$。
#include <bits/stdc++.h> #define rep(i, x, y) for(register int i = x; i < y; i++) #define REP(i, x, y) for(register int i = x; i <= y; i++) using namespace std; const int N = 2e5 + 5; int n, m, k, ans; int a[N], dis1[N], dis2[N]; vector<int> G[N]; queue<int> que; inline bool cmp(int x, int y) { return dis1[x] < dis1[y]; } int main() { ios::sync_with_stdio(false); cin >> n >> m >> k; REP(i, 1, k) cin >> a[i]; REP(i, 1, m) { int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } memset(dis1, -1, sizeof dis1); dis1[1] = 0; que.push(1); while(!que.empty()) { int u = que.front(); que.pop(); for(int v : G[u]) if(dis1[v] == -1) { dis1[v] = dis1[u] + 1; que.push(v); } } memset(dis2, -1, sizeof dis2); dis2[n] = 0; que.push(n); while(!que.empty()) { int u = que.front(); que.pop(); for(int v : G[u]) if(dis2[v] == -1) { dis2[v] = dis2[u] + 1; que.push(v); } } sort(a + 1, a + k + 1, cmp); ans = 0; rep(i, 1, k) ans = max(ans, min(dis1[n], min(dis1[a[i]] + dis2[a[i + 1]], dis1[a[i + 1]] + dis2[a[i]]) + 1)); cout << ans << endl; return 0; }
Solution 2
比如我们的边添加在 $u, v$ 之间。
一条 必须经过 $left< u, v ight>$ 的 $1 o n$ 路径长度怎么表示(不经过那么这条边就没意义了)?
$$ min(dis_{1 u} + dis_{2 v} + 1, dis_{1 v} + dis_{2 u} + 1)$$
原来最短路的长度显然是 $dis_{1 n}$,然后分类讨论一下。
1. 如果 $dis_{1 u} + dis_{2 v} + 1 le dis_{1 v} + dis_{2 u} + 1$
这时,新路径的长度为 $dis_{1 u} + dis_{2 v} + 1$。
路径的缩短长度 $Delta = dis_{1 n} - (dis_{1 u} + dis_{2 v} + 1)$,我们想要 $Delta$ 尽可能小。
$Delta = dis_{1 n} - dis_{1 u} - dis_{2 v} - 1$
$quad = dis_{1 n} - 1 - (dis_{1 u} + dis_{2 v})$
显然,$(dis_{1 u} + dis_{2 v})$ 取到 $max$ 时,$Delta$ 最小。
2. 如果 $dis_{1 u} + dis_{2 v} + 1 ge dis_{1 v} + dis_{2 u} + 1$
这时,新路径的长度为 $dis_{1 v} + dis_{2 u} + 1$。
路径的缩短长度 $Delta = dis_{1 n} - (dis_{1 v} + dis_{2 u} + 1)$,我们想要 $Delta$ 尽可能小。
$Delta = dis_{1 n} - dis_{1 v} - dis_{2 u} - 1$
$quad = dis_{1 n} - 1 - (dis_{1 v} + dis_{2 u})$
显然,$(dis_{1 v} + dis_{2 u})$ 取到 $max$ 时,$Delta$ 最小。
贪心吗?注意,我们虽然是想要一个东西取到 $max$,但是这是两种情况,我们满足了一种情况的 $max$ 时很可能会发现它的条件并不满足这种情况。
所以,排序一遍直接输出的想法告吹了 QwQ。
那么只好在满足条件的范围内取值。
第一种情况的条件移项后其实是什么?$dis_{1 u} - dis_{2 u} le dis_{1 v} - dis_{2 v}$。
第二种情况呢?$dis_{1 u} - dis_{2 u} > dis_{1 v} - dis_{2 v}$。
所以,我们只要把 $a$ 按照 $dis_1 - dis_2$ 升序排序,枚举一个 $a_i$ 作为 $v$,它前面的都是第一种情况,后面的都是第一种情况。所以,维护 前缀 $dis_1 max$ 作为 $u$,和 后缀 $dis_2 max$ 作为 $u$,都试一下就行了!
时间复杂度也是 $mathcal O(n log n)$。
#include <bits/stdc++.h> #define rep(i, x, y) for(register int i = x; i < y; i++) #define REP(i, x, y) for(register int i = x; i <= y; i++) #define PER(i, x, y) for(register int i = x; i >= y; i--) using namespace std; const int N = 2e5 + 5; int n, m, k, ans; int a[N], dis1[N], dis2[N], pre[N], suf[N]; vector<int> G[N]; queue<int> que; inline bool cmp(int x, int y) { return dis1[x] - dis2[x] < dis1[y] - dis2[y]; } int main() { ios::sync_with_stdio(false); cin >> n >> m >> k; REP(i, 1, k) cin >> a[i]; REP(i, 1, m) { int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } memset(dis1, -1, sizeof dis1); dis1[1] = 0; que.push(1); while(!que.empty()) { int u = que.front(); que.pop(); for(int v : G[u]) if(dis1[v] == -1) { dis1[v] = dis1[u] + 1; que.push(v); } } memset(dis2, -1, sizeof dis2); dis2[n] = 0; que.push(n); while(!que.empty()) { int u = que.front(); que.pop(); for(int v : G[u]) if(dis2[v] == -1) { dis2[v] = dis2[u] + 1; que.push(v); } } sort(a + 1, a + k + 1, cmp); REP(i, 1, k) pre[i] = max(pre[i - 1], dis1[a[i]]); PER(i, k, 1) suf[i] = max(suf[i + 1], dis2[a[i]]); REP(i, 1, k) { if(i > 1) ans = max(ans, min(dis1[n], dis2[a[i]] + 1 + pre[i - 1])); if(i < k) ans = max(ans, min(dis1[n], dis1[a[i]] + 1 + suf[i + 1])); } cout << ans << endl; return 0; }