【LibreOJ 154 集合划分计数】【集合幂级数+多项式k-exp】

题意

有一个大小为 n n n 的集合 S S S S S S 的子集族 F = { S 0 , S 1 , ⋯   , S m − 1 } {F}=\{S_0,S_1,\cdots,S_{m-1}\} F={S0,S1,,Sm1}。要求从 F F F 中选出不超过 k k k 个集合,使得这些集合的并为 S S S,且两两的交为空。问有多少种不同的选择方案。
k ≤ n ≤ 21 , m ≤ 262144 k\le n\le 21,m\le 262144 kn21,m262144

分析

f f f F F F 对应的集合幂级数,若定义乘法为子集卷积,那么要求的就是 g = exp ⁡ ≤ k f = ∑ i = 0 k f i i ! g=\exp_{\le k}f=\sum_{i=0}^k\frac{f^i}{i!} g=expkf=i=0ki!fi

先通过 FMT 转化为每个分量上形式幂级数的计算,发现 g g g 满足 g ′ = f ′ g − f k k ! f ′ g'=f'g-\frac{f^k}{k!}f' g=fgk!fkf

则可以通过递推来求 g g g。具体来说,若令 f ( i ) f(i) f(i) 表示 f f f i i i 次项次数,则 ( n + 1 ) g ( n + 1 ) = g ′ ( n ) = ∑ i = 0 n ( f ′ ( i ) g ( n − i ) − 1 k ! f k ( i ) f ′ ( n − i ) ) (n+1)g(n+1)=g'(n)=\sum_{i=0}^n\Big(f'(i)g(n-i)-\frac{1}{k!}f^k(i)f'(n-i)\Big) (n+1)g(n+1)=g(n)=i=0n(f(i)g(ni)k!1fk(i)f(ni))

这里要先求出 f k f^k fk。多项式的次幂也可以通过 n 2 n^2 n2 递推求得:令 G = F k G=F^k G=Fk,则 G ′ = k F k − 1 F ′ G'=kF^{k-1}F' G=kFk1F

F G ′ = k F k F ′ = k G F ′ FG'=kF^kF'=kGF' FG=kFkF=kGF

从而 ∑ i = 0 n F n − i G i + 1 ( i + 1 ) = k ∑ i = 0 n G n − i F i + 1 ( i + 1 ) \sum_{i=0}^nF_{n-i}G_{i+1}(i+1)=k\sum_{i=0}^nG_{n-i}F_{i+1}(i+1) i=0nFniGi+1(i+1)=ki=0nGniFi+1(i+1)

得到 G n + 1 = k ∑ i = 0 n ( i + 1 ) G n − i F i + 1 − ∑ i = 0 n − 1 ( i + 1 ) F n − i G i + 1 ( n + 1 ) F 0 G_{n+1}=\frac{k\sum_{i=0}^n(i+1)G_{n-i}F_{i+1}-\sum_{i=0}^{n-1}(i+1)F_{n-i}G_{i+1}}{(n+1)F_0} Gn+1=(n+1)F0ki=0n(i+1)GniFi+1i=0n1(i+1)FniGi+1

时间复杂度 O ( n 2 2 n ) O(n^22^n) O(n22n)

代码

#include
using namespace std;

typedef long long LL;

const int N = 23;
const int M = (1 << 21) + 5;
const int MOD = 998244353;

int n, m, k, bin[N], inv[N], ifac[N], cnt[M], a[N][M], f[N], g[N], tmp[N];

int ksm(int x, int y)
{
	int ans = 1;
	while (y)
	{
		if (y & 1) ans = (LL)ans * x % MOD;
		x = (LL)x * x % MOD; y >>= 1;
	}
	return ans;
}

void pre()
{
	inv[0] = inv[1] = ifac[0] = ifac[1] = 1;
	for (int i = 2; i <= n; i++) inv[i] = (LL)(MOD - MOD / i) * inv[MOD % i] % MOD;
	for (int i = 2; i <= n; i++) ifac[i] = (LL)ifac[i - 1] * inv[i] % MOD;
	bin[0] = 1;
	for (int i = 1; i <= n; i++) bin[i] = bin[i - 1] * 2;
	for (int i = 1; i < bin[n]; i++) cnt[i] = cnt[i >> 1] + (i & 1);
}

void FMT(int *a, int f)
{
	for (int i = 0; i < n; i++)
		for (int j = 0; j < bin[n]; j++)
			if (j & bin[i]) (a[j] += f > 0 ? a[j ^ bin[i]] : MOD - a[j ^ bin[i]]) %= MOD;
}

void poly_pow(int * f, int n, int k, int * g)
{
	for (int i = 0; i <= n; i++) g[i] = 0;
	int p = 0;
	while (p <= n && !f[p]) p++;
	if (p * k > n) return;
	int * nf = f + p, * ng = g + p * k, nn = n - p * k;
	ng[0] = ksm(nf[0], k);
	int inv0 = ksm(nf[0], MOD - 2);
	for (int i = 1; i <= nn; i++)
	{
		ng[i] = 0;
		for (int j = 0; j < i; j++) (ng[i] += (LL)ng[j] * nf[i - j] % MOD * (i - j) % MOD) %= MOD;
		ng[i] = (LL)ng[i] * k % MOD;
		for (int j = 1; j < i; j++) (ng[i] += MOD - (LL)nf[j] % MOD * ng[i - j] % MOD * (i - j) % MOD) %= MOD;
		ng[i] = (LL)ng[i] * inv0 % MOD * inv[i] % MOD;
	}
}

void k_exp(int * f, int n, int k, int * g)
{
	poly_pow(f, n, k, tmp);
	for (int i = 0; i <= n; i++) tmp[i] = (LL)tmp[i] * ifac[k] % MOD;
	g[0] = 0;
	for (int i = 0, w = 1; i <= k; i++) (g[0] += (LL)w * inv[i] % MOD) %= MOD, w = (LL)w * f[0] % MOD;
	for (int i = 1; i <= n; i++)
	{
		g[i] = 0;
		for (int j = 0; j < i; j++)
		{
			(g[i] += (LL)g[j] * f[i - j] % MOD * (i - j) % MOD) %= MOD;
			(g[i] += MOD - (LL)tmp[j] * f[i - j] % MOD * (i - j) % MOD) %= MOD;
		}
		g[i] = (LL)g[i] * inv[i] % MOD;
	}
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	pre();
	for (int i = 0, x; i < m; i++) scanf("%d", &x), a[cnt[x]][x]++;
	for (int i = 1; i <= n; i++) FMT(a[i], 1);
	for (int i = 0; i < bin[n]; i++)
	{
		for (int j = 0; j <= n; j++) f[j] = a[j][i];
		k_exp(f, n, k, g);
		for (int j = 0; j <= n; j++) a[j][i] = g[j];
	}
	FMT(a[n], -1);
	printf("%d\n", a[n][bin[n] - 1]);
	return 0;
}

你可能感兴趣的:(集合幂级数)