题目大意
给定一个 $n$ 个点 $m$ 条边的带权有向图,问图中最短的负环的长度。
数据范围:$n \le 300$。
思路分析
令 $\text{dp}(k, i, j)$ 表示从 $i$ 走 $k$ 步到 $j$,经过的边权和的最小值。我们暴力地对它进行转移,$k$ 每次加一时我们都要使用一次类似矩阵乘法的操作(将乘号换成加号,将求和换成取 $\max$)。我们一直进行转移,直到存在 $\text{dp}(k, i, i) < 0$ 时我们就找到了一个长度为 $k$ 的负环。时间复杂度 $O(n^4)$,不足以通过本题。
但是我们发现矩阵乘法是可以通过倍增的方法优化的。我们令 $\text{dp}(k, i, j)$ 表示从 $i$ 走 $2^k$ 步到 $j$,经过的边权和的最小值。这样我们就可以倍增地在 $O(n^3 \log n)$ 的时间内算出所有这样的矩阵。
在计算答案的时候,我们使用类似倍增找 LCA 的方法,按 $k$ 由大到小的顺序贪心地尝试乘上当前答案矩阵,再看结果矩阵是否有 $\text{dp}(i, i) < 0$。如果没有,则这次尝试是成功的。最后输出一共乘上矩阵的次数 $+1$ 即可。时间复杂度 $O(n^3 \log n)$。
代码实现
1 |
|
2 |
|
3 | using namespace std; |
4 | |
5 | const int maxn = 300, logn = 8, inf = 1e9 + 1; |
6 | int n, m, f[logn + 3][maxn + 3][maxn + 3], g[maxn + 3][maxn + 3], t[maxn + 3][maxn + 3]; |
7 | |
8 | int main() { |
9 | scanf("%d %d", &n, &m); |
10 | for (int i = 1; i <= n; i++) { |
11 | for (int j = 1; j <= n; j++) { |
12 | f[0][i][j] = g[i][j] = (i == j ? 0 : inf); |
13 | } |
14 | } |
15 | for (int i = 1, u, v, w; i <= m; i++) { |
16 | scanf("%d %d %d", &u, &v, &w); |
17 | if (w < f[0][u][v]) { |
18 | f[0][u][v] = w; |
19 | } |
20 | } |
21 | for (int d = 1; d <= logn; d++) { |
22 | for (int i = 1; i <= n; i++) { |
23 | for (int j = 1; j <= n; j++) { |
24 | f[d][i][j] = inf; |
25 | for (int k = 1; k <= n; k++) { |
26 | f[d][i][j] = min(f[d][i][j], f[d - 1][i][k] + f[d - 1][k][j]); |
27 | } |
28 | } |
29 | } |
30 | } |
31 | int ans = 0; |
32 | bool ok = false; |
33 | for (int d = logn; ~d; d--) { |
34 | bool flag = false; |
35 | for (int i = 1; i <= n; i++) { |
36 | for (int j = 1; j <= n; j++) { |
37 | t[i][j] = inf; |
38 | for (int k = 1; k <= n; k++) { |
39 | t[i][j] = min(t[i][j], g[i][k] + f[d][k][j]); |
40 | } |
41 | if (i == j && t[i][j] < 0) { |
42 | flag = true; |
43 | break; |
44 | } |
45 | } |
46 | if (flag) { |
47 | break; |
48 | } |
49 | } |
50 | if (!flag) { |
51 | ans += 1 << d; |
52 | for (int i = 1; i <= n; i++) { |
53 | for (int j = 1; j <= n; j++) { |
54 | g[i][j] = t[i][j]; |
55 | } |
56 | } |
57 | } else { |
58 | ok = true; |
59 | } |
60 | } |
61 | printf("%d\n", ok ? ans + 1 : 0); |
62 | return 0; |
63 | } |