Loading

[ABC386G] Many MST

极好的计数练习题。

首先需要一个 trick:\(x = \sum\limits_{0 \le i < x} 1\),看上去很没用,但是处理复杂信息时非常有效,因为我们可以进一步转化:\(\sum x = \sum\limits_x\sum\limits_{0 \le i < x} 1 = \sum\limits_{i \ge 0} \sum\limits_{x > i} 1 = \sum\limits_{i \ge 0}\sum [x > i]\)

于是对于这题,我们先把边权都 \(-1\) 使得其在 \([0, M - 1]\) 范围内,最后需要加上 \((N - 1) \times M^{\frac{N(N - 1)}{2}}\)。那么根据上面的结论,对于一张图 \(G\),记 \(c(G_k)\) 为只保留边权 \(< k\) 的边形成的连通块数量,我们有 MST 边权和为 \(\sum\limits_{k=1}^M \sum [x > k - 1]=\sum\limits_{k=1}^M c(G_k) - 1\)。则答案为

\[(N - 1) \times M^{\frac{N(N - 1)}{2}} - M \times M^{\frac{N(N - 1)}{2}}+\sum\limits_{k=1}^M \sum\limits_G c(G_k) \]

对于一个连通块,设其有 \(V\) 个点,有 \(E\) 条边边权 \(<k\),这 \(E\) 条边方案数为 \(k^E\),对于一条一个端点在连通块内,另一个端点在连通块外的边,边权一定 \(\ge k\),方案数为 \((M - k)^{V(N-V)}\),另外完全在连通块外部的边是乱填的,有 \(M^{\frac{(N-V)(N-V-1)}{2}}\) 种,内部但边权 \(\ge k\) 的有 \((M-k)^{\frac{V(V-1)}{2}-E}\) 种。

全部乘起来得到:

\[k^E \times (M-k)^{\frac{V(V-1)-E}{2}} \times (M - k)^{V(N-V)} \times M^{\frac{(N-V)(N-V-1)}{2}} \]

后面两项之和 \(V\) 有关,可以在枚举 \(V\) 时直接统计,前两项可以用 \(f(s)\) 表示 \(s\) 个点的 \(k^E \times (M-k)^{\frac{V(V-1)-E}{2}}\) 之和,转移考虑容斥,乱填方案数为 \(M^{\frac{s(s-1)}{2}}\),减去内部有一个更小的连通块的方案数,即

\[f(s) = M^{\frac{s(s-1)}{2}} - \sum\limits_{i=1}^{s-1}\binom{s}{i}f(i) \times (M - k)^{i(s-i)} \times M^{\frac{(s-i)(s-i-1)}{2}} \]

但是这样会算重,因为可能一种方案是有多个小连通块,就被算了多次,那我们可以直接钦定当前选的这个连通块里面一定要包含一个当前编号最小的点,这样就不会算重了。

\[f(s) = M^{\frac{s(s-1)}{2}} - \sum\limits_{i=1}^{s-1}\binom{s-1}{i-1}f(i) \times (M - k)^{i(s-i)} \times M^{\frac{(s-i)(s-i-1)}{2}} \]

最后得到答案:

\[(N - M - 1) \times M^{\frac{N(N - 1)}{2}} + \sum\limits_{k=1}^M \sum\limits_{s=1}^N \binom{N}{s} f(s) \times (M - k)^{s(N-s)} \times M^{\frac{(N-s)(N-s-1)}{2}} \]

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 5e2 + 10, M = N * (N - 1) >> 1, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    cout << arg << ' ';
    dbg(args...);
}
namespace Loop1st {
int n, m;
ll pw[N][M], f[N], C[N][N];
void main() {
    cin >> n >> m;
    C[0][0] = pw[0][0] = 1;
    for (int i = 1; i < N; i++) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
        pw[i][0] = 1;
        for (int j = 1; j < M; j++) pw[i][j] = pw[i][j - 1] * i % mod;
    }
    ll ans = (n - m - 1) * pw[m][n * (n - 1) >> 1] % mod;
    for (int k = 1; k <= m; k++) {
        for (int s = 1; s <= n; s++) {
            f[s] = pw[m][s * (s - 1) >> 1];
            for (int i = 1; i < s; i++) f[s] = (f[s] + mod - C[s - 1][i - 1] * f[i] % mod * pw[m - k][i * (s - i)] % mod * pw[m][(s - i) * (s - i - 1) >> 1] % mod) % mod;
            ans = (ans + C[n][s] * f[s] % mod * pw[m - k][s * (n - s)] % mod * pw[m][(n - s) * (n - s - 1) >> 1] % mod) % mod;
        }
    }
    cout << ans << '\n';
}

}
int main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T = 1;
    // cin >> T;
    while (T--) Loop1st::main();
    return 0;
}
posted @ 2025-12-30 08:22  循环一号  阅读(2)  评论(0)    收藏  举报