题意: T ( 1 ≤ T ≤ 15 ) T(1\leq T\leq 15) T(1≤T≤15)组测试,每组测试给 n n n个点 m m m条有向边 ( u → v , w ) (u\to v,w) (u→v,w), ( 1 ≤ n ≤ 500 , 0 ≤ m ≤ n ( n − 1 ) , 1 ≤ u , v ≤ n , 1 ≤ w ≤ 1 0 9 ) (1\leq n \leq 500, 0\leq m \leq n(n-1), 1\leq u,v\leq n, 1\leq w\leq 10^9) (1≤n≤500,0≤m≤n(n−1),1≤u,v≤n,1≤w≤109),(并未保证无重边、自环),你需要求最小环的长度和数量 ( m o d 998244353 ) \pmod {998244353} (mod998244353)。保证 ∑ n ≤ 5000 \sum n \leq 5000 ∑n≤5000。
题解:
要求最小环,不难想到
F
l
o
y
d
Floyd
Floyd最短路。用
d
i
s
dis
dis表示距离矩阵,将
d
i
s
[
i
]
[
j
]
dis[i][j]
dis[i][j]全部初始化为
i
n
f
inf
inf(不要将
d
i
s
[
i
]
[
i
]
dis[i][i]
dis[i][i]初始化为
0
0
0),将边存入
d
i
s
dis
dis,跑完
F
l
o
y
d
Floyd
Floyd,得到的
d
i
s
[
i
]
[
i
]
dis[i][i]
dis[i][i]即为以
i
i
i为起点的最小环。
还要计数,设
c
n
t
[
i
]
[
j
]
=
i
cnt[i][j]=i
cnt[i][j]=i到
j
j
j的最短路的数量,在跑
F
l
o
y
d
Floyd
Floyd的过程中,若
d
i
s
[
i
]
[
k
]
+
d
i
s
[
k
]
[
j
]
<
d
i
s
[
i
]
[
j
]
dis[i][k]+dis[k][j]<dis[i][j]
dis[i][k]+dis[k][j]<dis[i][j],则根据乘法原理,i到j的最短路数量有
c
n
t
[
i
]
[
k
]
∗
c
n
t
[
k
]
[
j
]
cnt[i][k]*cnt[k][j]
cnt[i][k]∗cnt[k][j]条
跑完Floyd后求出最小环长度 m n = min i = 1 n d i s [ i ] [ i ] mn=\mathop{\text{min}}\limits_{i=1}^n dis[i][i] mn=i=1minndis[i][i],再求最小环数量 a n s = ∑ i ∣ d i s [ i ] [ i ] = m n c n t [ i ] [ i ] ans=\sum\limits_{i|dis[i][i]=mn}cnt[i][i] ans=i∣dis[i][i]=mn∑cnt[i][i],此时就会发现数量算多了,因为根据当前这个方法,对于一个 x x x元环,这 x x x个点都会把它算上一次。
现在需要想办法,让每个环只被一个起点计数一次,而不是被每个起点计数。不难想到一种删点的方法:求出以 i i i为起点的最小环数量,删除 i i i点,不断循环直到点被删完。
这个做法看似复杂度过大,实则在
F
l
o
y
d
Floyd
Floyd的过程中已经得到。我们再仔细想一下
F
l
o
y
d
Floyd
Floyd算法的动态规划原理:
状态:
d
i
s
[
k
]
[
i
]
[
j
]
=
i
dis[k][i][j]=i
dis[k][i][j]=i到
j
j
j,除了
i
j
ij
ij以外只经过了
1
∼
k
1\sim k
1∼k的最短路
初态:
d
i
s
[
0
]
[
i
]
[
j
]
=
i
dis[0][i][j]=i
dis[0][i][j]=i到
j
j
j的边权
状态转移方程:
d
i
s
[
k
]
[
i
]
[
j
]
=
m
i
n
(
d
i
s
[
k
−
1
]
[
i
]
[
j
]
,
d
i
s
[
k
−
1
]
[
i
]
[
k
]
+
d
i
s
[
k
−
1
]
[
k
]
[
j
]
)
dis[k][i][j]=min(dis[k-1][i][j],dis[k-1][i][k]+dis[k-1][k][j])
dis[k][i][j]=min(dis[k−1][i][j],dis[k−1][i][k]+dis[k−1][k][j])
空间优化:随着
k
k
k增大,
d
i
s
[
k
]
[
i
]
[
j
]
dis[k][i][j]
dis[k][i][j]单调递减,可以优化掉第一个维度
k
k
k。
假如我们现在记录了 d i s [ k ] [ i ] [ j ] = i dis[k][i][j]=i dis[k][i][j]=i到 j j j除了 i j ij ij以外只经过了 1 ∼ k 1\sim k 1∼k的最短路, c n t [ k ] [ i ] [ j ] = i cnt[k][i][j]=i cnt[k][i][j]=i到 j j j除了 i j ij ij以外只经过了 1 ∼ k 1\sim k 1∼k的最短路数量,那么从大到小枚举 k k k,若 d i s [ k ] [ k ] [ k ] dis[k][k][k] dis[k][k][k]等于最小环,则答案加上 c n t [ k ] [ k ] [ k ] cnt[k][k][k] cnt[k][k][k],当 k k k变为 k − 1 k-1 k−1时,相当于删除了第 k k k个点,再询问以第 k − 1 k-1 k−1个点为起点的最小环。
但是这样需要开 O ( n 3 ) O(n^3) O(n3)的空间,这题 n ≤ 500 n\leq 500 n≤500,空间过大(PS:这题 10 10 10组 50 0 3 500^3 5003简直有毒)。此时不难意识到,这个 d i s [ k ] [ k ] [ k ] dis[k][k][k] dis[k][k][k]的 k k k从大到小枚举,和从小到大实际上是一样的道理,可以在 F l o y d Floyd Floyd的过程中统计答案,优化掉 k k k这一维的空间。 F l o y d Floyd Floyd算法的某个 k k k的迭代结束后,更新最小环大小,若 d i s [ k ] [ k ] = m n dis[k][k]=mn dis[k][k]=mn则数量加上 c n t [ k ] [ k ] cnt[k][k] cnt[k][k],若最小环大小被更新,则之前累计的数量清空。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 505, mod = 998244353;
const ll inf = 1e15;
int n, m;
ll dis[N][N], cnt[N][N];
void work() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
dis[i][j] = inf;
cnt[i][j] = 0;
}
}
for (int i = 1; i <= m; ++i) {
int x, y;
ll z;
cin >> x >> y >> z;
if (z < dis[x][y]) {
dis[x][y] = z;
cnt[x][y] = 1;
}
else if (z == dis[x][y]) {
++cnt[x][y];
}
}
ll mn = inf, ans = 0;
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
ll t = dis[i][k] + dis[k][j];
if (t < dis[i][j]) {
dis[i][j] = t;
cnt[i][j] = cnt[i][k] * cnt[k][j] % mod;
}
else if (t == dis[i][j]) {
cnt[i][j] += cnt[i][k] * cnt[k][j];
cnt[i][j] %= mod;
}
}
}
for (int i = 1; i <= n; ++i) {
if (dis[i][i] < mn) {
mn = dis[i][i];
ans = 0;
}
}
if (dis[k][k] == mn) {
ans += cnt[k][k];
}
}
ans = (ans % mod + mod) % mod;
if (mn == inf) {
cout << "-1 -1\n";
}
else {
cout << mn << " " << ans << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--) {
work();
}
}