无向连通图计数的一些想法
前两天遇到了一个题,机房老哥打算把它出成对答案的计数,中间有一步需要求恰有奇数条边的连通图个数。然后就有人提出推广到对每个 \(0\leq i<k\) 求 \(\bmod k\equiv i\) 的个数,大致编出来一个 \(\mathcal O(nk\log n)\) 的做法。
Problem. 给出正整数 \(n,k\),请对每个 \(0\leq i<2^k\) 求出有多少个 \(n\) 个点的带标号无向连通图满足边数 \(\bmod 2^k\equiv i\)。
\(n2^k\leq 10^6\),答案对 \(998244353\) 取模。
sol.
考虑记 \(f_{i,j}\) 为 \(i\) 个点 \(j\) 条边的无向图个数,\(g_{i,j}\) 为 \(i\) 个点 \(j\) 条边的无向连通图个数。考虑其生成函数,\(F\) 其实是 \(G\) 在 \(x\) 一维上做 EGF 卷积,\(y\) 一维上做 OGF 卷积的结果。记 \(\tilde F=\sum_{i,j}f_{i,j}\frac{x^iy^j}{i!},\tilde G=\sum_{i,j}g_{i,j}\frac{x^iy^j}{i!}\),那么实际上有 \(\tilde F=\exp\tilde G\)。
那么我们现在要求的就是 \([x^n]\ln \tilde F\bmod (y^{2^k}-1)\)。注意到那个模实际上是循环卷积,于是等价于对 \(y\) 一维做长为 \(2^k\) 的 FFT,然后求 \(\ln\),最后再 FFT 回来。直接暴力模拟这个过程即可做到 \(\mathcal O(n2^k\log n)\)。
当 \(k\) 足够大的时候,这其实给出了一个优于传统斯特林容斥的 \(\mathcal O(n^3\log n)\) 的做法,必可活用于下一次。
#include "poly.h"
int n, k;
int main() {
n = getll(), k = getll();
std::vector<poly> F(1 << k);
for(int i = 0; i < (1 << k); ++i) F[i].redeg(n);
ll w = fsp(gen, mod - 1 >> k);
for(int i = 0; i <= n; ++i) {
poly G; G.redeg((1 << k) - 1);
ll cur = 1;
for(int j = 0; j < (1 << k); ++j)
G[j] = fsp(1 + cur, 1ll * i * (i - 1) / 2), cur = cur * w % mod;
for(int j = 0; j < (1 << k); ++j) F[j][i] = G[j];
}
poly ans; ans.redeg((1 << k) - 1);
for(int i = 0; i < (1 << k); ++i) {
for(int j = 0; j <= n; ++j) F[i][j] = F[i][j] * Math::Inv(j) % mod;
ans[i] = F[i].ln()[n] * Math::Fac(n) % mod;
}
Poly::NTT(ans, k, -1), ans.print((1 << k) - 1);
}