题意:T(1≤T≤15)T(1\leq T\leq 15)T(1≤T≤15)组测试,每组测试给nnn个点mmm条有向边(u→v,w)(u\to v,w)(u→v,w),(1≤n≤500,0≤m≤n(n−1),1≤u,v≤n,1≤w≤109)(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),(并未保证无重边、自环),你需要求最小环的长度和数量(mod998244353)\pmod {998244353}(mod998244353)。保证∑n≤5000\sum n \leq 5000∑n≤5000。
题解:
要求最小环,不难想到FloydFloydFloyd最短路。用disdisdis表示距离矩阵,将dis[i][j]dis[i][j]dis[i][j]全部初始化为infinfinf(不要将dis[i][i]dis[i][i]dis[i][i]初始化为000),将边存入disdisdis,跑完FloydFloydFloyd,得到的dis[i][i]dis[i][i]dis[i][i]即为以iii为起点的最小环。
还要计数,设cnt[i][j]=icnt[i][j]=icnt[i][j]=i到jjj的最短路的数量,在跑FloydFloydFloyd的过程中,若dis[i][k]+dis[k][j]<dis[i][j]dis[i][k]+dis[k][j]<dis[i][j]dis[i][k]+dis[k][j]<dis[i][j],则根据乘法原理,i到j的最短路数量有cnt[i][k]∗cnt[k][j]cnt[i][k]*cnt[k][j]cnt[i][k]∗cnt[k][j]条
跑完Floyd后求出最小环长度mn=mini=1ndis[i][i]mn=\mathop{\text{min}}\limits_{i=1}^n dis[i][i]mn=i=1minndis[i][i],再求最小环数量ans=∑i∣dis[i][i]=mncnt[i][i]ans=\sum\limits_{i|dis[i][i]=mn}cnt[i][i]ans=i∣dis[i][i]=mn∑cnt[i][i],此时就会发现数量算多了,因为根据当前这个方法,对于一个xxx元环,这xxx个点都会把它算上一次。
现在需要想办法,让每个环只被一个起点计数一次,而不是被每个起点计数。不难想到一种删点的方法:求出以iii为起点的最小环数量,删除iii点,不断循环直到点被删完。
这个做法看似复杂度过大,实则在FloydFloydFloyd的过程中已经得到。我们再仔细想一下FloydFloydFloyd算法的动态规划原理:
状态:dis[k][i][j]=idis[k][i][j]=idis[k][i][j]=i到jjj,除了ijijij以外只经过了1∼k1\sim k1∼k的最短路
初态:dis[0][i][j]=idis[0][i][j]=idis[0][i][j]=i到jjj的边权
状态转移方程: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])dis[k][i][j]=min(dis[k−1][i][j],dis[k−1][i][k]+dis[k−1][k][j])
空间优化:随着kkk增大,dis[k][i][j]dis[k][i][j]dis[k][i][j]单调递减,可以优化掉第一个维度kkk。
假如我们现在记录了dis[k][i][j]=idis[k][i][j]=idis[k][i][j]=i到jjj除了ijijij以外只经过了1∼k1\sim k1∼k的最短路,cnt[k][i][j]=icnt[k][i][j]=icnt[k][i][j]=i到jjj除了ijijij以外只经过了1∼k1\sim k1∼k的最短路数量,那么从大到小枚举kkk,若dis[k][k][k]dis[k][k][k]dis[k][k][k]等于最小环,则答案加上cnt[k][k][k]cnt[k][k][k]cnt[k][k][k],当kkk变为k−1k-1k−1时,相当于删除了第kkk个点,再询问以第k−1k-1k−1个点为起点的最小环。
但是这样需要开O(n3)O(n^3)O(n3)的空间,这题n≤500n\leq 500n≤500,空间过大(PS:这题101010组5003500^35003简直有毒)。此时不难意识到,这个dis[k][k][k]dis[k][k][k]dis[k][k][k]的kkk从大到小枚举,和从小到大实际上是一样的道理,可以在FloydFloydFloyd的过程中统计答案,优化掉kkk这一维的空间。FloydFloydFloyd算法的某个kkk的迭代结束后,更新最小环大小,若dis[k][k]=mndis[k][k]=mndis[k][k]=mn则数量加上cnt[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();
}
}
文章讲述了使用Floyd最短路算法来解决图中的最小环问题,包括如何计算环的长度和数量,以及处理过程中涉及到的动态规划和空间优化策略。最后,提供了具体的代码实现来演示算法的运用。
270

被折叠的 条评论
为什么被折叠?



