ABC297G

aa90f43d-ec14-48ea-a73e-fa3106f72548

ABC297G

前言

其实不算太难,也是一道非常非常妙的题目。

题目转换

求 $n$ 个有标号的点,连 $m$ 条边的二分图数量(允许重边自环)。其中 $n \leq 30$,$m \leq 10^9$。

题解

显然,不可能有自环,重边也没啥用。

所以令 $f_{n,m}$ 表示 $n$ 个点,$m$ 条边的无重边二分图数量。

容易想到一个求法,就是枚举二分图左右的点的个数,然后在里面选择若干条边,将这种计数记到 $g_{n,m}$ 里面,即$$ g_{n,m} = \sum_{i=0}^{n} \binom{n}{i} \binom{i\times(n-i)}{m}。 $$ 这种计数对答案会计算重复,考虑 $g_{n,m}$ 表达的是什么?

求 $g_{n,m}$ 中,我们是枚举一种染色方案,然后看有多少种图满足。也就是说 $g_{n,m}$ 的意思是 $n$ 个点,$m$ 条边组成的图的染色方案数的和。

为了去重+套路想法,我们记录 $h_{n,m}$ 表示 $n$ 个点,$m$ 条边组成的连通图的染色方案数的和。

显然,我们可以枚举最后一个点所在联通块的大小,求出 $h_{n,m}$,即$$ h_{n,m} = g_{n,m} - \sum_{i=1}^{n-1}\sum_{j=0}^m \binom{n-1}{i - 1} \times h_{i,j} \times g_{n-i,m-j} $$ 注意到(下面很重要),联通图的染色方案要不是 $0$(不是二分图),要不是 $2$(是二分图),即除以 2 即可得到连通图的二分图数量。

得知了 $f_{n,m}$ 的连通图下的情况,剩下的就又是套路,即$$ f_{n,m} = \frac{h_{n,m}}{2} + \sum_{i=1}^{n-1} \sum_{j=0}^{m}\binom {n-1}{i-1} \dfrac{h_{i,j}}{2}f_{n-i,m-j} $$ 那么,没有重边的方案数算好了,重边就很好算了。

具体而言,相当于将 m 条边,分配到 k 种边里面,每种至少要一条边分配,直接容斥即可。

时间复杂度 $O(n^6)$,可通过。

注意事项

  1. 因为原题是求序列,所以要乘 $2^m$;
  2. 取模时减法,加法,乘法稍微注意一下即可。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 2010;
const int mod = 998244353;
//const int inv2 = 

int c[N][N], inv2;
int f[N][N], g[N][N], h[N][N];

int ksm (int x, int y) {
    int res = 1;
    while (y) {
        if (y & 1) res = 1ll * res * x % mod;
        x = 1ll * x * x % mod; y >>= 1;
    }
    return res;
}

void initc (int n) {
    for (int i = 0; i <= n; ++i) {
        c[i][0] = c[i][i] = 1;
        for (int j = 1; j < i; ++j) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
    }
}

int strling (int n, int m) {
    int sum = 0;
    for (int k = 0; k <= m; ++k) {
        int s = 1ll * c[m][k] * ksm (m - k, n) % mod;
        if (k & 1) sum = (sum - s + mod) % mod; else sum = (sum + s) % mod;
    }
    return sum;
}

void dpg (int n, int m) { 
    for (int i = 0; i <= n; ++i) g[n][m] = (g[n][m] + 1ll * c[n][i] * c[i * (n - i)][m] % mod) % mod;
//  if (n == 3 && m == 0) for (int i = 0; i <= n; ++i) printf ("%d %d\n", c[n][i], c[i * (n - i)][m]);
}

void dph (int n, int m) {
    h[n][m] = g[n][m];
//  if (n == 3 && m == 0) printf ("%d\n", g[3][0]);
    for (int i = 1; i < n; ++i) {
        for (int j = 0; j <= m; ++j) h[n][m] = (h[n][m] - 1ll * c[n - 1][i - 1] * h[i][j] % mod * g[n - i][m - j] % mod + mod) % mod;
//      if (n == 3 && m == 0) {
//          for (int j = 0; j <= m; ++j) printf ("%d %d\n", h[i][j], g[n - i][m - j]);
//      }
    }
}

void dpf (int n, int m) {
    f[n][m] = 1ll * h[n][m] * inv2 % mod;
    for (int i = 1; i < n; ++i) {
        for (int j = 0; j <= m; ++j) f[n][m] = (f[n][m] + 1ll * c[n - 1][i - 1] * h[i][j] % mod * f[n - i][m - j] % mod * inv2 % mod) % mod;
    }
}

int main() {
    initc(N - 5); inv2 = ksm (2, mod - 2);
    int n, m; scanf ("%d%d", &n, &m); int k = n * (n - 1) / 2;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= k; ++j) dpg(i, j);
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= k; ++j) dph(i, j);
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= k; ++j) dpf(i, j);
    } int ans = 0;
//  for (int j = 0; j <= k; ++j) printf ("%d ", g[n][j]); printf ("\n");
    for (int j = 0; j <= k; ++j) ans = (ans + 1ll * f[n][j] * strling(m, j) % mod) % mod;
    printf ("%d\n", 1ll * ans * ksm (2, m) % mod);
    return 0;
}

posted @ 2023-11-07 19:53  wangzhongyuan  阅读(6)  评论(0编辑  收藏  举报  来源