集合并卷积的三种求法(分治乘法,快速莫比乌斯变换(FMT),快速沃尔什变换(FWT))

也许更好的阅读体验


本文主要内容是对武汉市第二中学吕凯风同学的论文《集合幂级数的性质与应用及其快速算法》的理解

定义

集合幂级数

为了更方便的研究集合的卷积,引入集合幂级数的概念
集合幂级数也是形式幂级数的一种,只是集合的一种表现形式,无需考虑收敛或发散的含义

定义一个集合 S S S 的集合幂级数为 f f f ,那么我们就可以把集合 S S S 表示为如下形式

<mstyle displaystyle="true" scriptlevel="0"> f = <munder> T S </munder> f T x T </mstyle> \begin{aligned}f=\sum _{T\subseteq S}f_{T}\cdot x^{T}\end{aligned} f=TSfTxT
f T f_T fT T T T这个集合幂级数的系数
简单来说就是用二进制表示集合的元素是否存在,并将其写成多项式的形式

约定

c = ( a , b ) c=\left(a,b\right) c=(a,b)表示将 a , b a,b a,b连起来组成 c c c
为了方便,将 f g f*g fg写成 f g fg fg

卷积运算

加法

<mstyle displaystyle="true" scriptlevel="0"> h = f + g </mstyle> \begin{aligned}h=f+g\end{aligned} h=f+g
那么
h S = f S + g S h_S=f_S+g_S hS=fS+gS

减法

<mstyle displaystyle="true" scriptlevel="0"> h = f g </mstyle> \begin{aligned}h=f-g\end{aligned} h=fg
那么
h S = f S g S h_S=f_S-g_S hS=fSgS

乘法

<mstyle displaystyle="true" scriptlevel="0"> h = f g </mstyle> \begin{aligned}h=f*g\end{aligned} h=fg
那么
<mstyle displaystyle="true" scriptlevel="0"> h S = <munder> i j S </munder> f i g j </mstyle> \begin{aligned}h_S=\sum_{i∘j\subseteq S}f_ig_j\end{aligned} hS=ijSfigj
其中 可以是与,或,异或运算

集合并卷积 就是 进行或运算
子集卷积 就是 进行与运算
集合对称差卷积 就是 进行异或运算


快速求法

加法和减法都可以在 O ( n ) O(n) O(n)时间复杂度内求出结果
对乘法,有一些优化的算法,以集合并卷积为例

分治

f f f 2 n 2^n 2n
考虑将其集合幂级数的第 n n n个元素提出来
f = f + x { n } f + f=f^-+x^{\{n\}}f^+ f=f+x{n}f+,可以知道 f f^- f为前 2 n 1 2^{n-1} 2n1项, f + f^+ f+为后 2 n 1 2^{n-1} 2n1项即 f f^- f的第 n n n个元素在二进制下为 0 0 0 f + f^+ f+的第 n n n个元素在二进制下为 1 1 1
<mstyle displaystyle="true" scriptlevel="0"> f g </mstyle> <mstyle displaystyle="true" scriptlevel="0"> = ( f + x { n } f + ) ( g + x { n } g + ) </mstyle> <mstyle displaystyle="true" scriptlevel="0"> </mstyle> <mstyle displaystyle="true" scriptlevel="0"> = f g + x { n } ( f g + + f + g + f + g + ) </mstyle> <mstyle displaystyle="true" scriptlevel="0"> </mstyle> <mstyle displaystyle="true" scriptlevel="0"> = f g + x { n } ( ( f + f + ) ( g + g + ) f g ) </mstyle> \begin{aligned}fg&amp;=\left( f^-+x^{\{n\}}f^{+}\right) \left( g^{-}+x^{\{n\}}g^{+}\right)\\ &amp;=f^-g^-+x^{\{n\}}\left( f^-g^{+}+f^{+}g^-+f^{+}g^{+}\right) \\ &amp;=f^-g^-+x^{\{n\}}\left(\left(f^-+f^+\right)\left(g^-+g^+\right)-f^-g^-\right) \end{aligned} fg=(f+x{n}f+)(g+x{n}g+)=fg+x{n}(fg++f+g+f+g+)=fg+x{n}((f+f+)(g+g+)fg)
这样计算 f g fg fg就只要计算 f g f^-g^- fg ( f + f + ) ( g + g + ) \left(f^-+f^+\right)\left(g^-+g^+\right) (f+f+)(g+g+)
此时已经没有 n n n这个元素了,因为
于是我们可以递归分治求解 f g fg fg
时间复杂度 O ( n 2 n ) O\left(n2^n\right) O(n2n)

C o d e \mathcal{Code} Code

void fold (int *f,int *g,int *h,int hlen)//hlen -> half len
{
	if (hlen==1)	return void(h[0]=f[0]*g[0]);
	for (int i=0;i<hlen;++i)	f[i+hlen]+=f[i],g[i+hlen]+=g[i];
	fold(f,g,h,hlen>>1),fold(g+hlen,g+hlen,h+hlen,hlen>>1);
	for (int i=0;i<hlen;++i)	f[i+hlen]-=f[i],g[i+hlen]-=g[i];
}

快速莫比乌斯变换(FMT)

对于一个集合幂级数 f f f,我们定义其快速莫比乌斯变换为集合幂级数 <mover accent="true"> f ^ </mover> \widehat f f ,使其系数满足
<mstyle displaystyle="true" scriptlevel="0"> <mover accent="true"> f ^ </mover> S = <munder> T S </munder> f T </mstyle> \begin{aligned}\widehat f_S=\sum_{T\subseteq S}f_{T}\end{aligned} f S=TSfT

由容斥原理,我们可以得到
<mstyle displaystyle="true" scriptlevel="0"> f S = <munder> T S </munder> ( 1 ) S T <mover accent="true"> f ^ </mover> T </mstyle> \begin{aligned}f_S=\sum_{T\subseteq S}\left(-1\right)^{|S|-|T|}\widehat f_T\end{aligned} fS=TS(1)STf T

考虑乘法 <mover accent="true"> h ^ </mover> = <mover accent="true"> f ^ </mover> <mover accent="true"> g ^ </mover> \widehat h=\widehat f\widehat g h =f g
<mstyle displaystyle="true" scriptlevel="0"> <mover accent="true"> h ^ </mover> s </mstyle> <mstyle displaystyle="true" scriptlevel="0"> = <munder> i S </munder> <munder> j S </munder> f i g j </mstyle> <mstyle displaystyle="true" scriptlevel="0"> </mstyle> <mstyle displaystyle="true" scriptlevel="0"> = ( <munder> i S </munder> f i ) ( <munder> j S </munder> g j ) </mstyle> <mstyle displaystyle="true" scriptlevel="0"> </mstyle> <mstyle displaystyle="true" scriptlevel="0"> = <mover accent="true"> f ^ </mover> S <mover accent="true"> g ^ </mover> S </mstyle> \begin{aligned}\widehat h_{s}&amp;=\sum _{i\subseteq S}\sum _{j\subseteq S}f_{i}g_{j}\\ &amp;=\left(\sum _{i\subseteq S}f_i\right)\left(\sum _{j\subseteq S}g_{j}\right)\\ &amp;=\widehat f_S \widehat g_S \end{aligned} h s=iSjSfigj=(iSfi)jSgj=f Sg S

那么,现在我们知道想办法怎么求 <mover accent="true"> f ^ </mover> \widehat f f <mover accent="true"> g ^ </mover> \widehat g g ,然后把它们的系数乘起来,就可以得到 <mover accent="true"> h ^ </mover> \widehat h h
然后再将其反演得到 f f f(因为容斥是肯定会超时的)

考虑递推
我们设 <mover accent="true"> f ^ </mover> S ( i ) \widehat f_S^{\left(i\right)} f S(i)使其满足
<mstyle displaystyle="true" scriptlevel="0"> <mover accent="true"> f ^ </mover> S ( i ) = <munder> T S </munder> [ ( S T ) { 1 , 2 , , i } ] f T </mstyle> \begin{aligned}\widehat f_S^{\left(i\right)}=\sum _{T\subseteq S}\left[ \left( S-T\right) \subseteq \left\{ 1,2,\ldots ,i\right\} \right] f_{T}\end{aligned} f S(i)=TS[(ST){1,2,,i}]fT
即若 i + 1 n i+1\sim n i+1n有元素属于 S S S,则必须要选择,而 1 i 1\sim i 1i中的元素可有可无
那么我们最终的 <mover accent="true"> f ^ </mover> S = <mover accent="true"> f ^ </mover> S ( n ) \widehat f_S=\widehat f_S^{\left(n\right)} f S=f S(n),所有的元素都是可有可无的,即它的子集都被包含在内了
考虑第 S S S中有没有 i i i这个元素

  • 没有,则 <mover accent="true"> f ^ </mover> S ( i ) = <mover accent="true"> f ^ </mover> S ( i 1 ) \widehat f_S^{\left(i\right)}=\widehat f_S^{\left(i-1\right)} f S(i)=f S(i1)
  • 有,那么 <mover accent="true"> f ^ </mover> S ( i ) = <mover accent="true"> f ^ </mover> S ( i 1 ) + <mover accent="true"> f ^ </mover> S i ( i 1 ) \widehat f^{\left( i\right) }_{S}=\widehat f^{\left( i-1\right) }_{S}+\widehat f^{\left( i-1\right) }_{S- i} f S(i)=f S(i1)+f Si(i1) S i S-i Si表示从 S S S这个集合中去掉 i i i这个元素,这个式子的后两项前者是第 i i i个元素一定被选了,后者则是第 i i i个元素一定没有被选

而要将其反演,我们考虑其逆过程,只需将所有加上来的全部减去即可

时间复杂度 O ( n 2 n ) O\left(n2^n\right) O(n2n)

C o d e \mathcal{Code} Code

上面做法都是二维数组
考虑先枚举 i i i再算所有 S S S的答案,只需一维数组即可

void FMT (int *a,int n)//n个元素
{
	int all=1<<n;
	for (int i=0;i<n;++i)
		for (int j=0;j<all;++j)
			if (j>>1&1)	a[j]+=a[j^(1<<i)];
}

void IFMT (int *a,int n)//n个元素
{
	int all=1<<n;
	for (int i=0;i<n;++i)
		for (int j=0;j<all;++j)
			if (j>>i&1)	a[j]-=a[j^(1<<i)];
}

快速沃尔什变换(FWT)

我们发现,在进行分治算法中,只需保留出 f , g f^-,g^- f,g ( f + f + ) , ( g + g + ) \left(f^-+f^+\right),\left(g^-+g^+\right) (f+f+),(g+g+)就可以算出答案了
可惜递归的常数相对来说太大,我们考虑将其写成循环的形式就可以得到 F W T FWT FWT

若不考虑分治写成循环,我们换一种方法理解 F W T FWT FWT,当然,这是另一种思路了,上面将递归改成循环的思路是正确的
上面的 f = f + f + f=f^-+f^+ f=f+f+,我们是始终让其满足这个条件的,所以在后面还减去了一个 f g f^-g^- fg
让我们跳出思维的局限,弄这么一个 f , g f&#x27;,g&#x27; f,g使其满足 ( f g ) = f g (fg)&#x27;=f&#x27;g&#x27; (fg)=fg,这样我们只要计算 f g f&#x27;g&#x27; fg,然后把它反演一下就可以得到 f g fg fg
这里呢
f = ( f , f + f + ) f&#x27;=\left(f^-,f^-+f^+\right) f=(f,f+f+)
为什么这样就可以呢
考虑 ( f g ) (fg)&#x27; (fg),根据上面的推导
( f g ) = ( f g , ( f + f + ) ( g + g + ) ) \left(fg\right)&#x27;=\left(f^-g^-,\left(f^-+f^+\right)\left(g^-+g^+\right)\right) (fg)=(fg,(f+f+)(g+g+))
再考虑 f g f&#x27;g&#x27; fg
f g = f g f&#x27;^-g&#x27;^-=f^-g^- fg=fg
f + g + = ( f + f + ) ( g + g + ) f&#x27;^+g&#x27;^+=\left(f^-+f^+\right)\left(g^-+g^+\right) f+g+=(f+f+)(g+g+)

所以这样是可以的
于是我们可以得到
f = ( f , f + f + ) f&#x27;=\left(f^-,f^-+f^+\right) f=(f,f+f+)
然后称这样的 f f&#x27; f叫做沃尔什变换
F W T ( f ) = F W T ( f , f + f + ) FWT\left(f\right)=FWT\left(f^-,f^-+f^+\right) FWT(f)=FWT(f,f+f+)

反演也很简单
即将多算的 f f^- f减去即可

C o d e \mathcal{Code} Code

void FWT (int *a,int n)
{
	for (int len=2;len<=n;len<<=1)
		for (int i=0,hlen=len>>1;i<n;i+=len)
			for (int j=i,k=j+hlen;j<k;++j)
				a[j+hlen]+=a[j];
}

void IFWT (int *a,int n)
{
	for (int len=2;len<=n;len<<=1)
		for (int i=0,hlen=len>>1;i<n;i+=len)
			for (int j=i,k=j+hlen;j<k;++j)
				a[j+hlen]-=a[j];
}

时间复杂度 O ( n 2 n ) O\left(n2^n\right) O(n2n)

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧
如能得到推荐博主就更开心了
您的鼓励是博主的动力

全部评论

相关推荐

头像
09-21 09:55
门头沟学院 Java
想玩飞盘的我刷牛客:不给自己发个offer?
点赞 评论 收藏
分享
头像
09-12 16:00
已编辑
山西大学 后端
感受我的光:Ai自动剔除非目标院校
投递能链集团等公司10个岗位
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务