E. Porto Vs. Benfica
题意说的很清楚了,这里就不再过多阐述。
有个很直接了当的算法就是,枚举每一个点,并且枚举它相邻的一条边,从 \(1\) 跑到这个点的最短路加上这个点不经过这条边到 \(n\) 的最短路之和。
设 \(dist(u, \, v)\) 表示 \(u, \, v\) 之间的最短路,\(g(u, \, v)\) 表示从 \(u\) 开始不经过边 \((u, \, v)\) 到达 \(n\) 的最短路,一个合法的答案显然为 \(dist(1, \, u) + \max\{g(u, \, v)\}\)。
显然警察希望割边的时候是费用最大,所以我们用 \(g(u)\) 表示所有 \(g(u, \, v)\) 的最大值,如果设 \(f(u)\) 表示从 \(u\) 点开始,满足题意的答案的最小值,那么如果 \(u, \, v\) 相连,枚举所有 \(u\) 的临接点 \(v\),我们有 \(f(u) = \min\limits_{(u, \, v) \in G}\{\max\{f(v) + 1, \, g(u)\}\}\),由于我们想要更新的 \(f(u)\) 尽可能小,所以我们可以贪心的拿 \(f(v)\) 更小的点去更新,当一个未使用的点是最小的时候,我们称这个点是确定的,我们用这个最小的点去更新其他未确定的点,方法类似 Dijkstra,因此这一步是好做的,问题是如何得到 \(g\) 的值。
考虑从 \(n\) 开始的一颗 BFS 树,这颗树的树边是最短路径必须经过的,所以警察割边一定只会割树边,所以我们希望走一个横叉边来尽可能达到最短,显然叶子节点的值一定为所有能走的横叉边中价值的最小值,为了不让向上更新时出现不合法的情况,我们给每个点记录 \((d, \, v)\) 的二元组,表示从当前节点 \(u\) 走向 \(v\) 花费为 \(d\),显然我们可以用一个小根堆存储这些二元组,答案就恰好为堆顶。
如果 \(u\) 在树上含有儿子 \(v\),我们显然可以走向 \(v\) 再走横叉边,所以需要把儿子的 \((d, \, v)\) 更新到父亲,价值为 \((d + 1, \, v)\),此时做启发式合并,用标记永久化维护,我们向上合并的时候,可能 \((d, \, v)\) 中 \(v\) 会变成 \(u\) 子树的一个节点,这是不合法的,我们可以利用并查集维护此信息,当堆顶不合法时弹掉即可。
综上,复杂度主要来源于启发式合并,总时间复杂度 \(O(n\log^2{m})\)。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10, INF = 0x3f3f3f3f;
int n, m;
int dist[N], f[N], g[N], p[N], delta[N];
vector<int> edge[N], tree[N];
map<PII, int> in_tree;
priority_queue<PII, vector<PII>, greater<PII>> h[N];int find(int x) {if (p[x] != x) p[x] = find(p[x]);return p[x];
}void bfs(int S) {vector<int> st(n + 1);queue<int> q;q.push(S), st[S] = 1;while (q.size()) {int u = q.front();q.pop();for (auto v : edge[u]) {if (st[v]) continue;dist[v] = dist[u] + 1;tree[u].push_back(v);in_tree[{u, v}] = in_tree[{v, u}] = 1;st[v] = 1, q.push(v);}}
}void dfs(int u) {for (auto v : edge[u]) {if (!in_tree.count({u, v})) {h[u].push({dist[v] + 1, v});}}for (auto v : tree[u]) {dfs(v);delta[v] ++ ;p[find(v)] = p[find(u)];if (h[u].size() < h[v].size()) swap(h[u], h[v]), swap(delta[u], delta[v]);while (h[v].size()) h[u].push({h[v].top().first + delta[v] - delta[u], h[v].top().second}), h[v].pop();}while (h[u].size() && find(u) == find(h[u].top().second)) h[u].pop();if (h[u].size()) g[u] = h[u].top().first + delta[u];else g[u] = INF;
}void dijkstra(int S) {vector<int> st(n + 1);memset(f, 0x3f, sizeof f);priority_queue<PII, vector<PII>, greater<PII>> heap;f[S] = 0, heap.push({f[S], S});while (heap.size()) {int u = heap.top().second;heap.pop();if (st[u]) continue;st[u] = 1;for (auto v : edge[u]) {if (f[v] > max(g[v], f[u] + 1)) {f[v] = max(g[v], f[u] + 1);heap.push({f[v], v});}}}
}void solve() {cin >> n >> m;for (int i = 1; i <= n; i ++ ) p[i] = i;for (int i = 1; i <= m; i ++ ) {int a, b;cin >> a >> b;edge[a].push_back(b);edge[b].push_back(a);}bfs(n);dfs(n);dijkstra(n);if (f[1] != INF) cout << f[1] << "\n";else cout << "-1\n";
}int main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int T = 1;// cin >> T;while (T -- ) solve();return 0;
}