HDOJ 7322 Circuit 最小环计数 —— 2023“钉耙编程”中国大学生算法设计超级联赛(4)(2023杭电多校第四场)

题意: T ( 1 ≤ T ≤ 15 ) T(1\leq T\leq 15) T(1T15)组测试,每组测试给 n n n个点 m m m条有向边 ( u → v , w ) (u\to v,w) (uv,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) (1n500,0mn(n1),1u,vn,1w109),(并未保证无重边、自环),你需要求最小环的长度和数量 ( m o d 998244353 ) \pmod {998244353} (mod998244353)。保证 ∑ n ≤ 5000 \sum n \leq 5000 n5000

题解:

要求最小环,不难想到 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=idis[i][i]=mncnt[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 1k的最短路
初态: 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[k1][i][j],dis[k1][i][k]+dis[k1][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 1k的最短路, 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 1k的最短路数量,那么从大到小枚举 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 k1时,相当于删除了第 k k k个点,再询问以第 k − 1 k-1 k1个点为起点的最小环。

但是这样需要开 O ( n 3 ) O(n^3) O(n3)的空间,这题 n ≤ 500 n\leq 500 n500,空间过大(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();
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值