题解:P11915 [PA 2025] 瞬间传送 / Teleport
https://www.luogu.com.cn/problem/P11915
需要观察到一个很厉害的贪心策略:如果钦定所有点的距离不大于
-
此时最优路径为 $i\to u\to v\to j$,判断一下这种方案是否不大于 $r$ 就可以了。 -
此时不管是走 $i\to u\to v\to j$ 还是 $i\to v\to u\to j$ 都不如走已经存在的 $i\to u\to j$ 这条路径,也就是说如果要走新边,代价是一定比原距离大,更是比 $r$ 大的;也就是说,$(u,v)$ 不能解决 $(i,j)$ 之间的问题,假设就不成立了。
综上,只需要判断
这里具体提一下需要摊的几个点:
- 枚举到
r 的时候用所有d(i,j)=r+1 把v 在i 处的最大d(v,j) 更新,方便后面O(n) 地 check。摊出来是O(n^3) 的。 - 枚举仍然处在合法队列里的
(u,v) ,如果 check 合法,就说明对于当前r 至少存在一个合法解,就可以break了;否则,把(u,v) 弹出,继续 check 下一条边。这样每条边只会被弹出一次,而未弹出边的 check 次数最多是O(n) ;加上O(n) 的 check,摊出来是O(n^3) 的。
#include <bits/stdc++.h>
const int inf = 0x3f3f3f3f;
int main() {
#ifdef ONLINE_JUDGE
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
std::freopen(".in", "r", stdin);
std::freopen(".out", "w", stdout);
#endif
int T;
for (std::cin >> T; T--; ) {
int n;
std::cin >> n;
std::vector<std::vector<int> > g(n + 1, std::vector<int> (n + 1, inf));
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
char t;
std::cin >> t;
if (t == '1' || i == j)
g[i][j] = t - '0';
}
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
if (i != k)
for (int j = 1; j <= n; ++j)
if (j != i && j != k)
g[i][j] = std::min(g[i][j], g[i][k] + g[k][j]);
std::queue<std::pair<int, int> > q;
std::vector<std::vector<std::pair<int, int> > > p(n + 1);
std::vector<std::vector<int> > mx(n + 1, std::vector<int> (n + 1));
for (int i = 1; i < n; ++i)
for (int j = i + 1; j <= n; ++j) {
q.emplace(i, j);
p[g[i][j] - 1].emplace_back(i, j);
}
auto check = [&](int u, int v, int r) {
for (int i = 1; i <= n; ++i) {
if (g[u][i] > g[v][i])
std::swap(u, v);
if (g[u][i] + mx[v][i] > r)
return false;
}
return true;
};
for (int r = n; r >= -1; --r) {
for (auto [i, j] : p[r])
for (int v = 1; v <= n; ++v) {
mx[v][i] = std::max(mx[v][i], g[v][j]);
mx[v][j] = std::max(mx[v][j], g[v][i]);
}
for (; !q.empty(); ) {
auto [u, v] = q.front();
if (!check(u, v, r))
q.pop();
else
break;
}
if (q.empty()) {
std::cout << r + 1 << '\n';
break;
}
}
}
return 0;
}