ABC 327G Many Good Tuple Problems 题解
思路
若建无向边 ,那么序列对 为好的当且仅当得到的图为二分图.
直接计数需要处理重边和边的排列,不是很好考虑.考虑求出 个点 条边的简单二分图数目,记作 ,枚举本质不同的边数 ,答案由下式给出:
其中 表示将 个互不相同的小球放入 个互不相同的盒子中,要求盒子非空的方案数.
考虑如何求出 .这个问题是经典的,可以利用二项式反演得出:
考虑如何求出 .有一个很 naive 的想法是,由于二分图可以被黑白染色,直接枚举黑色节点数 ,此时二部可以连 条边,总方案数为:
不妨记上式为 ,实际上我们计算的是将 个点黑白染色后,连 条边使得边的两端颜色不同的方案数.由于每个联通块都有两种染色方式,所以对于一个有 个联通块的二分图,它被计算了 次,太坏了!
若我们令这种图的联通块个数为常数 ,可以直接将方案数除去 得到二分图个数,而最简单的方式是令 ,即对联通的这种图计数.不妨设将 个点黑白染色后,连 条边使得边的两端颜色不同,且图联通的方案数为 .考虑容斥掉不合法的方案数,有:
其中 枚举的是 所在的联通块大小, 枚举的是这个联通块的边数,二项式系数的含义是从去除 的 个编号中选出 个编号,分配给 所在的联通块中的其他点.
现在 个点 条边的联通二分图个数就是 ,那么 的计算也可以通过枚举 所在的联通块大小得到:
代码
#include <cstdio>
#define debug(...) fprintf(stderr, __VA_ARGS__)
inline int rd() {
int x = 0, f = 1, c = getchar();
while (((c - '0') | ('9' - c)) < 0)
f = c != '-', c = getchar();
while (((c - '0') | ('9' - c)) > 0)
x = x * 10 + c - '0', c = getchar();
return f ? x : -x;
}
using i64 = long long;
const i64 P = 998244353;
const i64 i2 = (P + 1) / 2;
i64 fpow(i64 b, i64 p) {
i64 res = 1;
for (; p; b = b * b % P, p >>= 1) {
if (p & 1) res = res * b % P;
}
return res;
}
const int N = 30;
const int M = N * N;
i64 C[M + 5][M + 5], S[M + 5][M + 5];
i64 fac[M + 5];
void pre() {
for (int i = 0; i <= M; i++) C[i][0] = 1;
for (int i = 1; i <= M; i++) {
for (int j = 1; j <= M; j++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P;
}
}
}
int n, m;
i64 f[N + 5][M + 5], g[N + 5][M + 5], h[N + 5][M + 5];
int main() {
pre();
n = rd(), m = rd();
int lim = (n / 2) * ((n + 1) / 2);
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= lim; j++) {
for (int k = 0; k <= i; k++) {
(g[i][j] += C[i][k] * C[k * (i - k)][j] % P) %= P;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= lim; j++) {
h[i][j] = g[i][j];
for (int k = 1; k < i; k++) {
for (int p = 0; p <= j; p++) {
(h[i][j] += P - C[i - 1][k - 1] * h[k][p] % P * g[i - k][j - p] % P) %= P;
}
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= lim; j++) {
(h[i][j] *= i2) %= P;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= lim; j++) {
f[i][j] = h[i][j];
for (int k = 1; k < n; k++) {
for (int p = 0; p <= j; p++) {
(f[i][j] += C[i - 1][k - 1] * h[k][p] % P * f[i - k][j - p] % P) %= P;
}
}
}
}
i64 ans = 0;
for (int i = 0; i <= lim; i++) {
i64 coef = 0;
for (int j = 0; j <= i; j++) {
if ((i - j) & 1) (coef += P - C[i][j] * fpow(j, m) % P) %= P;
else (coef += C[i][j] * fpow(j, m) % P) %= P;
}
(ans += coef * f[n][i] % P) %= P;
}
printf("%lld\n", ans * fpow(2, m) % P);
return 0;
}