前两天遇到了一个题,机房老哥打算把它出成对答案的计数,中间有一步需要求恰有奇数条边的连通图个数。然后就有人提出推广到对每个 (0leq i<k) 求 (mod kequiv i) 的个数,大致编出来一个 (mathcal O(nklog n)) 的做法。
Problem. 给出正整数 (n,k),请对每个 (0leq i<2^k) 求出有多少个 (n) 个点的带标号无向连通图满足边数 (mod 2^kequiv i)。
(n2^kleq 10^6),答案对 (998244353) 取模。
sol.
考虑记 (f_{i,j}) 为 (i) 个点 (j) 条边的无向图个数,(g_{i,j}) 为 (i) 个点 (j) 条边的无向连通图个数。考虑其生成函数,(F) 其实是 (G) 在 (x) 一维上做 EGF 卷积,(y) 一维上做 OGF 卷积的结果。记 ( ilde F=sum_{i,j}f_{i,j}frac{x^iy^j}{i!}, ilde G=sum_{i,j}g_{i,j}frac{x^iy^j}{i!}),那么实际上有 ( ilde F=exp ilde G)。
那么我们现在要求的就是 ([x^n]ln ilde Fmod (y^{2^k}-1))。注意到那个模实际上是循环卷积,于是等价于对 (y) 一维做长为 (2^k) 的 FFT,然后求 (ln),最后再 FFT 回来。直接暴力模拟这个过程即可做到 (mathcal O(n2^klog n))。
当 (k) 足够大的时候,这其实给出了一个优于传统斯特林容斥的 (mathcal O(n^3log 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);
}