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

文章讲述了使用Floyd最短路算法来解决图中的最小环问题,包括如何计算环的长度和数量,以及处理过程中涉及到的动态规划和空间优化策略。最后,提供了具体的代码实现来演示算法的运用。

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

题解:

要求最小环,不难想到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]=ijjj的最短路的数量,在跑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=idis[i][i]=mncnt[i][i],此时就会发现数量算多了,因为根据当前这个方法,对于一个xxx元环,这xxx个点都会把它算上一次。

现在需要想办法,让每个环只被一个起点计数一次,而不是被每个起点计数。不难想到一种删点的方法:求出以iii为起点的最小环数量,删除iii点,不断循环直到点被删完。

这个做法看似复杂度过大,实则在FloydFloydFloyd的过程中已经得到。我们再仔细想一下FloydFloydFloyd算法的动态规划原理:
状态:dis[k][i][j]=idis[k][i][j]=idis[k][i][j]=ijjj,除了ijijij以外只经过了1∼k1\sim k1k的最短路
初态:dis[0][i][j]=idis[0][i][j]=idis[0][i][j]=ijjj的边权
状态转移方程: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[k1][i][j],dis[k1][i][k]+dis[k1][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]=ijjj除了ijijij以外只经过了1∼k1\sim k1k的最短路,cnt[k][i][j]=icnt[k][i][j]=icnt[k][i][j]=ijjj除了ijijij以外只经过了1∼k1\sim k1k的最短路数量,那么从大到小枚举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-1k1时,相当于删除了第kkk个点,再询问以第k−1k-1k1个点为起点的最小环。

但是这样需要开O(n3)O(n^3)O(n3)的空间,这题n≤500n\leq 500n500,空间过大(PS:这题1010105003500^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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值