浅谈集合幂级数

叠甲:读者很菜。

集合幂级数是一个很厉害的东西。

我们对于是有限集的全集 \(\mathbb{U}={1,2,\dots n}\),我们利用占位符 \(x^S\) 来表示一个序列 \(f\),其中对于 \(S\subseteq \mathbb{U}\) 的值为 \(f_S\)

一般记为 \(F=\sum\limits_{S\subseteq \mathbb{U}}f_Sx^S\)

对于占位符的运算,有 \(x^S\times x^T=\begin{cases}x^{S\cup T}&,S\cap T=\varnothing\\0&,S\cap T\neq \varnothing\end{cases}\)

子集卷积

我们考虑最基本的卷积运算:

已知 \(F=\sum\limits_{S\subseteq \mathbb{U}}f_Sx^S\)\(G=\sum\limits_{S\subseteq \mathbb{U}}g_Sx^S\) 如何求解 \(H=F\times G\)

【模板】子集卷积

如果运算有 \(x^S\times x^T=x^{S\cup T}\),这就是普通的或卷积,可以 \(O(n2^n)\) 实现。

但是并不是,只有在 \(S\cap T=\varnothing\) 的时候才会做贡献,考虑在或卷积的基础上增加一些修改,使得其满足这个条件。

我们知道 \(|S|+|T|\ge |S\cup T|\),取等的时候当且仅当 \(S\cap T=\varnothing\),这就完美的满足了我们的条件。

所以我们添加一个占位符 \(y\)\(x^S\) 变成 \(x^Sy^|S|\),则 \((x^Sy^{|S|})\times (x^Ty^{|T|})=x^{S\cup T}y^{|S|+|T|}\),这样就完美的符合了我们的要求。

具体的实现,就是增加以为,这一位的运算是朴素的多项式卷积。

多项式卷积使用暴力算法可以做到 \(O(n^22^n)\),可以用 FFT/NTT 优化到 \(O(n\log n2^n)\),但是常数太大,完全没有必要。

void mul(int *F,int *G,int *H)
{
	memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
	for(int i=0;i<st;i++) f[pct[i]][i]=F[i],g[pct[i]][i]=G[i];
	for(int i=0;i<=n;i++) FMT(f[i]),FMT(g[i]);
	for(int S=0;S<st;S++)
	{
		for(int i=n;~i;i--)
		{
			g[i][S]=1ll*g[i][S]*f[0][S]%Mod;
			for(int j=1;j<=i;j++)
				add(g[i][S],1ll*f[j][S]*g[i-j][S]%Mod);
		}
	}
	for(int i=0;i<=n;i++) iFMT(g[i]);
	for(int i=0;i<st;i++) H[i]=g[pct[i]][i];
}

子集卷积 exp

我们来考虑上面卷积运算的一些组合意义:

如果我们想要查询有 \(f\) 中两个不交集合构成集合对应的方案数,其为 \(\dfrac{F\times F}{2}=\dfrac{F^2}{2}\)

依此类推选择 \(k\) 个构成的不交集合就是 \(\dfrac{F^k}{k!}\)(除 \(k!\) 的原因是选择这些集合的相对顺序是无关的)。

我们考虑选择若干个不交集合(考虑可以不选),有 \(G=x^\varnothing+\sum\limits_{k=1}\dfrac{F^k}{k!}\)

发现 \(\sum\limits_{k=1}\dfrac{F^k}{k!}\),这个东西就是 \(\exp F-1\)。也就是说 \(G=\exp F\)

因此,还是在 \(x\) 维上做或卷积,在 \(y\) 维上我们做多项式 \(exp\),我们就可以通过 \(F\) 来生成 \(G=\exp F\) 了。

\(G=\exp F\),所以 \(G'=(\exp F)'\)

所以 \(G'=\exp F\times F'\Rightarrow G'=G\times F\)

\(\sum\limits_{i}ig_iy^{i-1}=(\sum\limits_{i}g_iy^i)\times (\sum\limits_{i}if_iy^{i-1})\)

所以 \(ng_n=\sum\limits_{i=1}^nf_i\times g_{n-i}\)。这样单次 \(exp\) 可以做到 \(O(n^2)\),所以整体就可以做到 \(O(n^22^n)\)

而子集卷积 exp 的组合意义就是:选择若干个不交集合组合在一起的所有方案

void exp(int *F,int *G)
{
	memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
	for(int i=1;i<st;i++) f[pct[i]][i]=F[i];
	for(int i=0;i<=n;i++) FMT(f[i]);
	for(int S=0,tmp;S<st;S++)
	{
		g[0][S]=1;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=i;j++)
				add(g[i][S],1ll*j*f[j][S]%Mod*g[i-j][S]%Mod);
			g[i][S]=1ll*g[i][S]*inv[i]%Mod;
		}
	}
	for(int i=0;i<=n;i++) iFMT(g[i]);
	for(int i=0;i<st;i++) G[i]=g[pct[i]][i];
}

子集卷积 ln

有 exp 就自然会有 ln。

既然 exp 是组合,那么 ln 就是拆分。

exp 是已知 \(f\) 得到 \(g\),ln 就是通过 \(g\) 得到 \(f\)

由于 \(ng_n=\sum\limits_{i=1}^nf_i\times g_{n-i}\),所以 \(ng_n=nf_ng_0+\sum\limits_{i=1}^{n-1}f_i\times g_{n-i}\)\(g_0=1\))。

\(f_n=g_n-\dfrac{1}{n}\sum\limits_{i=1}^{n-1}f_i\times g_{n-i}\)

这样实现的复杂度也是 \(O(n^22^n)\) 的。

组合意义就是:拆分成若干个不交集合

void ln(int *F,int *G)
{
	memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
	for(int i=1;i<st;i++) f[pct[i]][i]=F[i];
	f[0][0]=1;
	for(int i=0;i<=n;i++) FMT(f[i]);
	for(int S=0,tmp;S<st;S++)
	{
		for(int i=1;i<=n;i++)
		{
			g[i][S]=f[i][S],tmp=0;
			for(int j=1;j<i;j++)
				add(tmp,1ll*j*g[j][S]%Mod*f[i-j][S]%Mod);
			del(g[i][S],1ll*tmp*inv[i]%Mod);
		}
	}
	for(int i=0;i<=n;i++) iFMT(g[i]);
	for(int i=0;i<st;i++) G[i]=g[pct[i]][i];
}
posted @ 2024-02-18 19:22  Xun_Xiaoyao  阅读(157)  评论(1编辑  收藏  举报
/* 鼠标点击求赞文字特效 */