AtCoder 327G 组合数学

题意

传送门 AtCoder 327G Many Good Tuple Problems

题解

( A i , B i ) (A_i, B_i) (Ai,Bi) 看作一条边并建图,序列对满足条件当且仅当所构造的图为二分图。

x ( n , k ) x(n,k) x(n,k) 代表代标号的节点数为 n n n 且边数为 k k k 的简单二分图的方案数; b ( m , k ) b(m,k) b(m,k) 代表将 m m m 条边依次分配到简单图的 k k k 条边上的方案数。根据容斥原理,考虑简单图上部分边没有分配 m m m 条边中至少一条的情况,可以得到
b ( m , k ) = ∑ i = 0 k ( − 1 ) i ( k − i ) m ( k i ) b(m,k)=\sum\limits_{i=0}^{k}(-1)^{i}(k-i)^{m}\binom{k}{i} b(m,k)=i=0k(1)i(ki)m(ik)

对于 x ( n , k ) x(n,k) x(n,k) 的求解,观察到联通二分图的染色方案只有两种,那么先求出可以黑白染色的二分连通图数量,最后像背包问题一样不断累加联通分量即可。具体而言,令 g ( n , k ) g(n,k) g(n,k) 为将图中节点黑白染色后,相同颜色的节点间不存在连边的方案数; f ( n , k ) f(n,k) f(n,k) 则为 g ( n , k ) g(n,k) g(n,k) 中联通图的数量,则有
g ( n , k ) = ∑ i = 0 n ( n i ) ( i ( n − i ) k ) g(n,k) = \sum\limits_{i=0}^{n}\binom{n}{i}\binom{i(n-i)}{k} g(n,k)=i=0n(in)(ki(ni))
枚举图中编号最小的节点所在联通分量的情况,则可以容斥得到
f ( n , k ) = g ( n , k ) − ∑ i = 1 n − 1 ∑ j = 0 k ( n − 1 i − 1 ) f ( i , j ) g ( n − i , k − j ) f(n,k)=g(n,k) - \sum\limits_{i=1}^{n-1}\sum\limits_{j=0}^{k}\binom{n-1}{i-1}f(i,j)g(n-i,k-j) f(n,k)=g(n,k)i=1n1j=0k(i1n1)f(i,j)g(ni,kj)
类似背包操作,逆容斥回去得到
x ( n , k ) = f ( n , k ) / 2 + ∑ i = 1 n − 1 ∑ j = 0 k ( n − 1 i − 1 ) f ( i , j ) / 2 ⋅ x ( n − i , k − j ) x(n,k) = f(n,k)/2 + \sum\limits_{i=1}^{n-1}\sum\limits_{j=0}^{k}\binom{n-1}{i-1}f(i,j)/2\cdot x(n-i,k-j) x(n,k)=f(n,k)/2+i=1n1j=0k(i1n1)f(i,j)/2x(ni,kj)
考虑 ( A i , B i ) (A_i, B_i) (Ai,Bi) 的有序性,每一条边贡献为 2 2 2,总贡献为 2 m 2^m 2m,则答案为
2 m ⋅ ∑ k = 0 l x ( n , k ) b ( m , k ) 2^m\cdot\sum\limits_{k=0}^{l}x(n,k)b(m,k) 2mk=0lx(n,k)b(m,k)
其中 l l l 为二分图边数的最大值,其等于 ⌊ n / 2 ⌋ ⋅ ⌈ n / 2 ⌉ \lfloor n/2\rfloor\cdot\lceil n/2\rceil n/2n/2。总时间复杂度 O ( n 4 log ⁡ m + n 6 ) O(n^4\log m + n^6) O(n4logm+n6)

#include 
using namespace std;
constexpr int MOD = 998244353;
using ll = long long;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    auto power = [&](ll x, int n) {
        ll res = 1;
        while (n > 0) {
            if (n & 1) {
                (res *= x) %= MOD;
            }
            (x *= x) %= MOD, n >>= 1;
        }
        return res;
    };
    int l = max(n, (n / 2) * ((n + 1) / 2));
    vector<vector<ll>> c(l + 1, vector<ll>(l + 1));
    for (int i = 0; i <= l; ++i) {
        c[i][0] = 1;
    }
    for (int i = 0; i < l; ++i) {
        for (int j = 0; j < l; ++j) {
            c[i + 1][j + 1] = (c[i][j + 1] + c[i][j]) % MOD;
        }
    }
    vector<ll> b(l + 1);
    for (int k = 0; k <= l; ++k) {
        for (int i = 0; i <= k; ++i) {
            b[k] += ((i & 1) ? -1 : 1) * power(k - i, m) * c[k][i] % MOD;
            b[k] %= MOD;
        }
    }

    vector<vector<ll>> g(n + 1, vector<ll>(l + 1));
    for (int nd = 0; nd <= n; ++nd) {
        for (int k = 0; k <= l; ++k) {
            for (int i = 0; i <= nd; ++i) {
                g[nd][k] += c[nd][i] * c[i * (nd - i)][k] % MOD;
                g[nd][k] %= MOD;
            }
        }
    }

    vector<vector<ll>> f(n + 1, vector<ll>(l + 1));
    for (int nd = 0; nd <= n; ++nd) {
        for (int k = 0; k <= l; ++k) {
            f[nd][k] = g[nd][k];
            for (int i = 1; i < nd; ++i) {
                for (int j = 0; j <= k; ++j) {
                    f[nd][k] -= f[i][j] * c[nd - 1][i - 1] % MOD * g[nd - i][k - j] % MOD;
                    f[nd][k] %= MOD;
                }
            }
        }
    }

    ll inv2 = power(2, MOD - 2);
    vector<vector<ll>> x(n + 1, vector<ll>(l + 1));
    for (int nd = 0; nd <= n; ++nd) {
        for (int k = 0; k <= l; ++k) {
            x[nd][k] = f[nd][k] * inv2 % MOD;
            for (int i = 1; i < nd; ++i) {
                for (int j = 0; j <= k; ++j) {
                    x[nd][k] += f[i][j] * inv2 % MOD * c[nd - 1][i - 1] % MOD * x[nd - i][k - j] % MOD;
                    x[nd][k] %= MOD;
                }
            }
        }
    }

    ll res = 0;
    for (int k = 0; k <= l; ++k) {
        res += x[n][k] * b[k] % MOD;
        res %= MOD;
    }
    (res += MOD) %= MOD;
    (res *= power(2, m)) %= MOD;
    cout << res << '\n';

    return 0;
}

你可能感兴趣的:(数学,算法)