QOJ #5145. Shortest Path 题解
Description
给定一个 \(n\) 个点 \(m\) 条边的无向图,每条边有边权。
令 \(f(i)\) 表示从 \(1\) 号点到达点 \(n\) 恰好包含 \(i\) 条边的最短路径长度。如果不存在这样的路径,那么认为 \(f(i) = 0\)。(路径不需要是简单路径,即一条边可以反复经过)
问 \(\sum_{i=1}^x f(i)\),输出答案对 \(998244353\) 取模的结果。
\(n\leq 2000,m\leq 5000,x\leq 10^9\)。
Solution
首先容易注意到 \(x\) 特别大的时候的最优解一定是形如找一条路径,并在这条路径边权最小的边上来回走。
对于不超过 \(4n\) 的时刻暴力做,大于 \(4n\) 的做下面的做法。
设 \(dis_{s\to t,k}\) 表示从 \(s\) 走到 \(t\),长度为 \(k\) 的路径的边权和最小值。那么我们枚举那条来回走的边 \((u,v,w)\),则对时刻 \(t\) 的贡献为 \(\displaystyle\min{\left\{dis_{1\to u,i}+dis_{v\to n,j}+w+w(n-i-j-1)\right\}}=\min\left\{(dis_{1\to u,i}-iw)+(dis_{v\to n,j}-jw)+nw\right\}\)。
由于钦定 \(t\) 很大,所以这里只关心 \(dis_{1\to u,i}-iw\) 和 \(dis_{v\to n,j}-jw\) 的最小值,预处理出 \(dis\) 后可以 \(O(n)\) 求出来这两个东西。
注意需要对于奇偶分开处理。枚举完边后,问题等价于问 \(\sum_{i=l}^{r}{\min_j\left\{i\cdot a_j+b_j\right\}}\),容易 \(O(m^2)\) 得到答案。
至于为什么阈值是 \(4n\),可能的解释是枚举来回走的边的两头的路径长度时需要考虑奇偶性的问题,由于单考虑长度为奇数或者长度为偶数的最短路的路径长度的上界是 \(2n\) 的,两边加起来就是 \(4n\)。
时间复杂度:\(O(nm+m^2)\)。
Code
#include <bits/stdc++.h>
#define int int64_t
const int kMaxN = 2e3 + 5, kMaxM = 2e4 + 5, kMod = 998244353;
int n, m, k;
int u[kMaxM], v[kMaxM], w[kMaxM];
int dis1[kMaxN][kMaxN * 4], dis2[kMaxN][kMaxN * 4];
int a[2][kMaxM], b[2][kMaxM];
std::vector<std::pair<int, int>> G[kMaxN];
void dijkstra(int s, int t, int dis[kMaxN][kMaxN * 4]) {
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= t; ++j)
dis[i][j] = 1e18;
dis[s][0] = 0;
for (int c = 0; c < t; ++c) {
for (int i = 1; i <= n; ++i) {
for (auto p : G[i]) {
int j = p.first, w = p.second;
dis[j][c + 1] = std::min(dis[j][c + 1], dis[i][c] + w);
}
}
}
}
std::pair<int, int> get(int k1, int b1, int k2, int b2) {
// k1 * x + b1 >= k2 * x + b2 的解的范围
// (k1 - k2) * x + (b1 - b2) >= 0
k1 -= k2, b1 -= b2;
// k1 * x + b1 >= 0
b1 = -b1;
// k1 * x >= b1
if (k1 == 0) return b1 <= 0 ? std::pair<int, int>{0, 1e18} : std::pair<int, int>{1e18, 0};
if (k1 > 0) return {std::max<int>(0, (b1 + k1 - 1) / k1), 1e18};
else return {0, (-b1) / (-k1)};
}
void dickdreamer() {
std::cin >> n >> m >> k;
for (int i = 1; i <= n; ++i) G[i].clear();
for (int i = 1; i <= m; ++i) {
std::cin >> u[i] >> v[i] >> w[i];
G[u[i]].emplace_back(v[i], w[i]), G[v[i]].emplace_back(u[i], w[i]);
}
dijkstra(1, std::min(k, 4 * n), dis1), dijkstra(n, std::min(k, 4 * n), dis2);
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
int ans = 0;
for (int i = 1; i <= std::min(k, 4 * n); ++i)
if (dis1[n][i] != 1e18)
ans += dis1[n][i];
ans %= kMod;
for (int i = 1; i <= m; ++i) {
int dis1[2] = {(int)1e18, (int)1e18};
int dis2[2] = {(int)1e18, (int)1e18};
int dis3[2] = {(int)1e18, (int)1e18};
int dis4[2] = {(int)1e18, (int)1e18};
for (int j = 0; j <= 2 * n; ++j) {
dis1[j & 1] = std::min(dis1[j & 1], ::dis1[u[i]][j] - j * w[i]);
dis2[j & 1] = std::min(dis2[j & 1], ::dis1[v[i]][j] - j * w[i]);
dis3[j & 1] = std::min(dis3[j & 1], ::dis2[u[i]][j] - j * w[i]);
dis4[j & 1] = std::min(dis4[j & 1], ::dis2[v[i]][j] - j * w[i]);
}
a[0][i] = a[1][i] = w[i];
b[0][i] = std::min({dis1[0] + dis4[1], dis1[1] + dis4[0], dis2[0] + dis3[1], dis2[1] + dis3[0]});
b[1][i] = std::min({dis1[0] + dis4[0], dis1[1] + dis4[1], dis2[0] + dis3[0], dis2[1] + dis3[1]});
}
for (int o = 0; o < 2; ++o) {
int sum = 0;
for (int i = 1; i <= m; ++i) {
int L = 4 * n + 1, R = k;
for (int j = 1; j <= m; ++j) {
if (j == i) continue;
int l, r;
if (j < i) std::tie(l, r) = get(a[o][j], b[o][j], a[o][i], b[o][i]);
else std::tie(l, r) = get(a[o][j], b[o][j] - 1, a[o][i], b[o][i]);
L = std::max(L, l), R = std::min(R, r);
}
if (b[o][i] < 1e17) {
int l = (L - 1 - o) / 2 + 1, r = (R - o) / 2;
if (l <= r) {
ans += (r - l + 1) * (b[o][i] % kMod) % kMod;
ans += (l + r) * (r - l + 1) % kMod * a[o][i] % kMod + o * (r - l + 1) * (a[o][i] % kMod) % kMod;
}
// for (int j = L; j <= R; ++j) {
// if (j % 2 == o) ans += (j * a[o][i] + b[o][i]) % kMod;
// }
}
if (L <= R) sum += R - L + 1;
}
// std::cerr << sum << ' ' << k - 4 * n << '\n';
// assert(sum == std::max<int>(0, k - 4 * n));
}
// for (int i = 4 * n + 1; i <= k; ++i) {
// int res = 1e18;
// for (int j = 1; j <= m; ++j) res = std::min(res, i * a[i & 1][j] + b[i & 1][j]);
// if (res < 1e16) ans += res % kMod;
// }
ans = (ans % kMod + kMod) % kMod;
std::cout << ans << '\n';
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

浙公网安备 33010602011771号