Problem Description
给出一个 n n n 个点, m m m 条边的非连通有向图(图中不存在自环和重边),每条边存在权值 w w w 表示该边的长度。
现在请求出图中环的最小长度以及最小长度环的数量。
Input
第一行输入 T ( T ≤ 15 ) T(T \le 15) T(T≤15) ,表示 T T T 组测试数据。
第二行输入 n ( 1 ≤ n ≤ 500 , 0 ≤ m ≤ n × ( n − 1 ) ) n(1 \le n \le 500,0 \le m \le n \times (n-1)) n(1≤n≤500,0≤m≤n×(n−1)) ,表示图中的节点个数和边数。
接下来 m m m 行输入 u i , v i , w i ( 1 ≤ u i , v i ≤ n , 1 ≤ w i ≤ 1 0 9 ) u_{i},v_{i},w_{i}(1 \le u_{i} , v_{i} \le n,1 \le w_{i} \le 10^9 ) ui,vi,wi(1≤ui,vi≤n,1≤wi≤109) 表示节点 u i u_{i} ui 向节点 v i v_{i} vi 有一条长度为 w i w_{i} wi 的有向边。
Output
对于每个测试样例,输出最小环的长度和个数,并对 998244353 998244353 998244353 取模。如果不存在换则输出 − 1 -1 −1 。
Solution
首先观察到 n ≤ 500 n \le 500 n≤500,很容易联想到这应该会是一个 n 3 n^3 n3 的算法。
那么我们就从节点入手,如果 u , v u,v u,v 之间的存在环,而且要求环最小,我们可以显然的发现这个最小环就是一条从 u 向 v 的最短路和一条从 v 向 u 的最短路(此处可用简单反证法证明)。
那么此处我们便可以采用 n 3 n^3 n3 的 F l o y d Floyd Floyd 或是跑 n n n 遍的 D i j k s t r a Dijkstra Dijkstra 来求得所有点之间的最短路,这样环的最小长度便可以得到。
当我们考虑最小环的数量时,可以在最短路转移时统计对应最短路的路径数量然后在处理最小环时计算。但当到了实际计算时我们发现简单的计算出现大量的重复计算边的情况,我们必须不重不漏地计算所有最小环。
此处,我们当然可以采用在做最短路时记录路径然后在计算答案时标记经过点或是经过路径来防止重复计算,但实际存边或点空间复杂度可能会达至 O ( n 3 ) O(n^3) O(n3) ,在此题中可能会 M L E MLE MLE ,需要通过一些操作来降低空间复杂度。
但此时请注意 F l o y d Floyd Floyd 的转移和其第一维表示:在经过标号小于等于 k k k , i i i 到 j j j 最短距离。
这意味着我们在第 k k k 轮时枚举标号 k k k 到 i ( i < k ) i (i<k) i(i<k) 的所有环时不会遍历到标号大于 k k k 的点,这样我们就可以在做完每一轮 F l o y d Floyd Floyd 之后更新最小环长度的同时枚举k的所有出边统计此时最小环的个数,这样我们便能不重不漏地得到最小环的数量(切勿直接统计 k k k 到 i ( i < k ) i (i<k) i(i<k) 的所有最小环,这样会有路径被重复统计,样例 1 1 1 便能看出)。
Code
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
constexpr ll inf = 1e18, mod = 998244353;
void solve() {
int n, m;
cin >> n >> m;
vector a(n + 1, vector<ll>(n + 1, inf));//a[i][j]表示i到j存在一条长度为a[i][j]的边,如果值为inf表示不存在
vector dis(n + 1, vector<ll>(n + 1, inf));//dis[i][j]表示i到j的最短路径
vector cnt(n + 1, vector<ll>(n + 1));//cnt[i][j]表示i到j的最短路径个数
for (int i = 1; i <= n; i++)dis[i][i] = 0;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
a[u][v] = w;
dis[u][v] = w;
cnt[u][v]++;
}
ll minn = inf, ans = 0;
//Floyd
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (dis[i][j] > dis[i][k] + dis[k][j]) {//转移最短路径时,统计路径个数
dis[i][j] = dis[i][k] + dis[k][j];
cnt[i][j] = cnt[i][k] * cnt[k][j] % mod;
}
else if (dis[i][j] == dis[i][k] + dis[k][j])
cnt[i][j] = (cnt[i][j] + cnt[i][k] * cnt[k][j] % mod) % mod;
}
}
for (int i = 1; i < k; i++) {//统计第k轮时标号k到小于k的标号的所有最小环
if (minn > a[k][i] + dis[i][k]) {
minn = a[k][i] + dis[i][k];
ans = cnt[i][k];
}
else if (minn == a[k][i] + dis[i][k])ans = (ans + cnt[i][k]) % mod;
}
}
if (minn == inf)cout << -1 << ' ' << -1 << endl;//不存在环
else cout << minn << ' ' << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)solve();
}