QOJ #5145. Shortest Path 题解

Description

给定一个 nn 个点 mm 条边的无向图,每条边有边权。

f(i)f(i) 表示从 11 号点到达点 nn 恰好包含 ii 条边的最短路径长度。如果不存在这样的路径,那么认为 f(i)=0f(i) = 0。(路径不需要是简单路径,即一条边可以反复经过)

i=1xf(i)\sum_{i=1}^x f(i),输出答案对 998244353998244353 取模的结果。

n2000,m5000,x109n\leq 2000,m\leq 5000,x\leq 10^9

Solution

首先容易注意到 xx 特别大的时候的最优解一定是形如找一条路径,并在这条路径边权最小的边上来回走。

对于不超过 4n4n 的时刻暴力做,大于 4n4n 的做下面的做法。

disst,kdis_{s\to t,k} 表示从 ss 走到 tt,长度为 kk 的路径的边权和最小值。那么我们枚举那条来回走的边 (u,v,w)(u,v,w),则对时刻 tt 的贡献为 min{dis1u,i+disvn,j+w+w(nij1)}=min{(dis1u,iiw)+(disvn,jjw)+nw}\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\}

由于钦定 tt 很大,所以这里只关心 dis1u,iiwdis_{1\to u,i}-iwdisvn,jjwdis_{v\to n,j}-jw 的最小值,预处理出 disdis 后可以 O(n)O(n) 求出来这两个东西。

注意需要对于奇偶分开处理。枚举完边后,问题等价于问 i=lrminj{iaj+bj}\sum_{i=l}^{r}{\min_j\left\{i\cdot a_j+b_j\right\}},容易 O(m2)O(m^2) 得到答案。

至于为什么阈值是 4n4n,可能的解释是枚举来回走的边的两头的路径长度时需要考虑奇偶性的问题,由于单考虑长度为奇数或者长度为偶数的最短路的路径长度的上界是 2n2n 的,两边加起来就是 4n4n

时间复杂度:O(nm+m2)O(nm+m^2)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#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;
}

QOJ #5145. Shortest Path 题解
https://sobaliuziao.github.io/2025/09/18/post/6faa0ee7.html
作者
Egg_laying_master
发布于
2025年9月18日
许可协议