快速沃尔什变化(FWT)详详详解

介绍

我们以前常见的多项式乘法长这个亚子:
C[k]=i+j=kA[i]×B[j] C[k]=\sum_{i+j=k}A[i]\times B[j]

FWTFWT 用来处理的,与 FFTFFT 稍稍不同,是这样的卷积:
C[k]=ij=kA[i]×B[j] C[k]=\sum_{i\oplus j=k}A[i]\times B[j]

其中,\oplus 可以是 &,,Λ\&,|,\Lambda,这三种符号分别对应 and,or,xorand,or,xor

为了方便,首先做一些约定(下面 A,BA,B 代表多项式):

A+B=(A[0]+B[0],A[1]+B[1],)A+B=(A[0]+B[0],A[1]+B[1],\cdots),即按位相加
AB=(A[0]B[0],A[1]B[1],)A-B=(A[0]-B[0],A[1]-B[1],\cdots),即按位相减
AB=(A[0]×B[0],A[1]×B[1],)A*B=(A[0]\times B[0],A[1]\times B[1],\cdots),即按位相乘(这个要注意,别和卷积搞混了)
AB=(ij=0A[i]×B[j] ,ij=1A[i]×B[j] ,)A\oplus B=(\sum\limits_{i\oplus j=0}A[i]\times B[j]~,\sum\limits_{i\oplus j=1}A[i]\times B[j]~,\cdots),本质就是上面的那个卷积

思路

大致思路与 FFTFFT 一致,将要卷在一起的两个多项式先正变换,然后按位相乘,最后逆变换回来即可。

或卷积

卷积的公式是这样的:C[k]=ij=kA[i]×B[j]C[k]=\sum\limits_{i|j=k}A[i]\times B[j]。可以表示为 C=ABC=A|B

其实或卷积与卷积的本质应该是 FMTFMT,但是大部分人将它们归在 FWTFWT 门下了,所以也放在这里讲。

正变换

定义: FWT(A)[i]=jiA[j]FWT(A)[i]=\sum_{j|i} A[j]。这个是正变换后得到的数组的意义,简单来说,就是下标的子集对应的位置之和,其中 jij|i 表示 jjii 的子集。


那么有一个很显然的性质:

FWT(A)+FWT(B)=FWT(A+B)FWT(A)+FWT(B)=FWT(A+B)

证明: 我们拿每一位单独看:

FWT(A)[i]=jiA[j]FWT(A)[i]=\sum_{j|i}A[j]
FWT(B)[i]=jiB[j]FWT(B)[i]=\sum_{j|i}B[j]
FWT(A+B)[i]=ji(A+B)[j]=jiA[j]+B[j]FWT(A+B)[i]=\sum_{j|i}(A+B)[j]=\sum_{j|i}A[j]+B[j]

嗯……很显然可以得到 FWT(A)[i]+FWT(B)[i]=FWT(A+B)[i]FWT(A)[i]+FWT(B)[i]=FWT(A+B)[i]


再定义一个东西:设 AA 这个多项式有 2n2^n 项,那么 A0A_0 表示前 2n12^{n-1} 项,A1A_1 表示后 2n12^{n-1} 项。

那么其实很容易得到递归的公式:
FWT(A)={(FWT(A0),FWT(A0)+FWT(A1))(n>0)A(n=0) FWT(A)= \begin{cases} (FWT(A_0),FWT(A_0)+FWT(A_1)) & (n>0)\\ A & (n=0) \end{cases}

你问我 (A,B)(A,B) 这样的表示是什么意思?别忘了 A,BA,B 是多项式,你可以认为这是将两个多项式拼接在了一起。就像 A=(A0,A1)A=(A_0,A_1)

证明

n=0n=0 时很显然,就不讲了。

仔细观察,A0A_0A1A_1 的区别在于,A0A_0 部分下标最高位是 00,而 A1A_1 的最高位是 11,也就是说,A0A_0 在对应位置上是 A1A_1 的子集,因为只有最高位不一样,其他位都一样,所以我们将 A0A_0 加给 A1A_1

又因为 A1A_1 中不可能有任何一位是 A0A_0 的任意一个位置的子集,所以左半部分就只是 FWT(A0)FWT(A_0)

这样递归下去,依次处理完第 n1n-1 位,第 n2n-2 位,……,第 11 位后,就求出了 FWT(A)FWT(A)

求解证明

求出 FWT(A)FWT(A)FWT(B)FWT(B) 之后,对应位置相乘就得到了 FWT(C)FWT(C),然后逆变换就可以得到多项式 CC

这里就需要证明,为什么 FWT(C)=FWT(AB)=FWT(A)FWT(B)FWT(C)=FWT(A|B)=FWT(A)*FWT(B)

感性理解

感性理解其实很简单,FWT(A)[i]FWT(A)[i] 记录的是 ii 的子集之和,FWT(B)[i]FWT(B)[i] 也是,那么他们相乘,其实就是双方子集中的每一个元素两两相乘,取出来的两个元素的下标或起来一定还是 ii 的子集中的一个,由于 C[i]C[i] 记录的是下标或起来为 ii 的乘积之和,那么全部乘完之后,我们不仅得到了 C[i]C[i],还得到了 ii 的所有子集的 CC,也就是 FWT(C)[i]FWT(C)[i]

理性证明

这玩意丑的很……相信没人想看的qwq,不过为了严谨,还是有必要写的啦。

下面为了区分属于…的子集或运算两个意思,就不都用 | 了,属于…的子集\in 代替(只是在这个证明中)。
FWT(C)[i]=jiC[j]=jixy=jA[x]×B[y]=(xy)iA[x]×B[y]=xiA[x]×yiB[y]=FWT(A)[i]×FWT(B)[i] \begin{aligned} FWT(C)[i]&=\sum_{j\in i}C[j]\\ &=\sum_{j\in i}\sum_{x|y=j}A[x]\times B[y]\\ &=\sum_{(x|y)\in i}A[x]\times B[y]\\ &=\sum_{x\in i}A[x]\times\sum_{y\in i}B[y]\\ &=FWT(A)[i]\times FWT(B)[i] \end{aligned}

逆变换

逆变换我们称为 IFWTIFWT,即满足 A=IFWT(FWT(A))A=IFWT(FWT(A))

这个其实只需要将上面的加号变成减号即可。

感性理解

可以类比前缀和来理解:

//知道每个位置的值求前缀和
for(int i=1;i<=n;i++)a[i]+=a[i-1];
//知道前缀和求每个位置的值
for(int i=n;i>=1;i--)a[i]-=a[i-1];

理性证明

其实证明也很简单,以及与卷积异或卷积的理性证明其实都差不多。

证明: 我们现在已知 FWT(A)0,FWT(A)1FWT(A)_0,FWT(A)_1,要求 A0,A1A_0,A_1

FWT(A)0=FWT(A0)\because FWT(A)_0=FWT(A_0)
A0=IFWT(FWT(A0))=IFWT(FWT(A)0)\therefore A_0=IFWT(FWT(A_0))=IFWT(FWT(A)_0)
FWT(A)1=FWT(A0)+FWT(A1)\because FWT(A)_1=FWT(A_0)+FWT(A_1),即 FWT(A1)=FWT(A)1FWT(A)0FWT(A_1)=FWT(A)_1-FWT(A)_0
A1=IFWT(FWT(A1))=IFWT(FWT(A)1FWT(A)0)\therefore A_1=IFWT(FWT(A_1))=IFWT(FWT(A)_1-FWT(A)_0)

代码实现(其实和 FFTFFT 很像):

void FWT_or(ll *f,int type)//type为1是正变换,-1是逆变换
{
	for(int mid=1;mid<n;mid<<=1)//每次将两块大小为mid的块合并成一个大块
	for(int block=mid<<1,j=0;j<n;j+=block)//枚举每个大块
	for(int i=j;i<j+mid;i++)//依次合并
	f[i+mid]=(f[i+mid]+f[i]*type+mod)%mod;
}

与卷积

卷积的公式是这样的:C[k]=i&j=kA[i]×B[j]C[k]=\sum\limits_{i\&j=k}A[i]\times B[j]

正变换

定义: FWT(A)[i]=ijA[j]FWT(A)[i]=\sum_{i|j}A[j],意思就是,FWT(A)[i]FWT(A)[i] 记录的是所有 A[j]A[j] 的和,其中 i,ji,j 满足 iijj 的子集。

这个显然也满足 FWT(A)+FWT(B)=FWT(A+B)FWT(A)+FWT(B)=FWT(A+B)

其实与卷积或卷积十分相似,一个是前面的对后面的产生贡献,一个是后面的对前面产生贡献。

类比或卷积,可以得到与卷积的递归公式:
FWT(A)={(FWT(A0)+FWT(A1),FWT(A1))(n>0)A(n=0) FWT(A)= \begin{cases} (FWT(A_0)+FWT(A_1),FWT(A_1)) & (n>0)\\ A & (n=0) \end{cases}

证明

这就不用证了吧……和或卷积基本上一毛一样,就是 A0A_0A1A_1 对应位置就差一个 11,所以 A0A_0A1A_1 子集,所以把 A1A_1 的贡献累加到 A0A_0 上。

求解证明

如果理解了或运算的求解证明,那么与运算的其实也搞定了。

这里就直接贴一个数学证明了:
FWT(C)[i]=ijC[j]=ijxy=jA[x]×B[y]=i(xy)A[x]×B[y]=ixA[x]×iyB[y]=FWT(A)[i]×FWT(B)[i] \begin{aligned} FWT(C)[i]&=\sum_{i\in j}C[j]\\ &=\sum_{i\in j}\sum_{x|y=j}A[x]\times B[y]\\ &=\sum_{i\in(x|y)}A[x]\times B[y]\\ &=\sum_{i\in x}A[x]\times\sum_{i\in y}B[y]\\ &=FWT(A)[i]\times FWT(B)[i] \end{aligned}

几乎就是一样的qwq

逆变换

也是很简单的将加号变减号。

证明: 我们现在已知 FWT(A)0,FWT(A)1FWT(A)_0,FWT(A)_1,要求 A0,A1A_0,A_1

FWT(A)1=FWT(A1)\because FWT(A)_1=FWT(A_1)
A1=IFWT(FWT(A1))=IFWT(FWT(A)1)\therefore A_1=IFWT(FWT(A_1))=IFWT(FWT(A)_1)
FWT(A)0=FWT(A0)+FWT(A1)\because FWT(A)_0=FWT(A_0)+FWT(A_1),即 FWT(A0)=FWT(A)1FWT(A)0FWT(A_0)=FWT(A)_1-FWT(A)_0
A0=IFWT(FWT(A0))=IFWT(FWT(A)0FWT(A)1)\therefore A_0=IFWT(FWT(A_0))=IFWT(FWT(A)_0-FWT(A)_1)

代码实现:

void FWT_and(ll *f,int type)
{
	for(int mid=1;mid<n;mid<<=1)
	for(int block=mid<<1,j=0;j<n;j+=block)
	for(int i=j;i<j+mid;i++)
	f[i]=(f[i]+f[i+mid]*type+mod)%mod;
}

异或卷积

这个东西才是真正的 FWTFWT,求法都奇奇怪怪的。

卷积的公式是这样的:C[k]=iΛj=kA[i]×B[j]C[k]=\sum\limits_{i\Lambda j=k}A[i]\times B[j]

正变换

首先摒弃上面带来的一些奇奇怪怪的想法,因为异或卷积的正变换没有用到异或,完全没有。

c(i)c(i) 表示 ii 的二进制有多少个 11,如 c(3)=2c(3)=2

奇怪的定义: FWT(A)[i]=j=02n1(1)c(i&j)A[j]FWT(A)[i]=\sum\limits_{j=0}^{2^n-1}(-1)^{c(i\&j)}A[j],也可以写成 FWT(A)[i]=(c(i&j)mod2=0A[j])(c(i&k)mod2=1A[k])FWT(A)[i]=(\sum\limits_{c(i\&j)\bmod 2=0}A[j])-(\sum\limits_{c(i\&k)\bmod 2=1}A[k])

没错,是 &\& 不是 Λ\Lambda。不用管为什么,看下去就知道了,重点是记住定义。

递归公式:
FWT(A)={(FWT(A0)+FWT(A1),FWT(A0)FWT(A1))(n>0)A(n=0) FWT(A)= \begin{cases} (FWT(A_0)+FWT(A_1),FWT(A_0)-FWT(A_1)) & (n>0)\\ A & (n=0) \end{cases}

怎么样,是不是有种见了鬼的感觉qwq

但是实际上不难理解。

证明

依然忽略 n=0n=0 时的情况~

此时要理解,求出来的 FWT(A1)FWT(A_1) 并没有考虑到他是 AA 的右半部分,即最高位的 11 是没有考虑的,也就是说,求 FWT(A1)FWT(A_1) 时下标是 00 ~ 2n112^{n-1}-1,而不是 2n12^{n-1} ~ 2n12^n-1

剩下的我们从左到右看:

首先 FWT(A)0=FWT(A0)+FWT(A1)FWT(A)_0=FWT(A_0)+FWT(A_1),因为 FWT(A)0FWT(A)_0 是左半部分,于是最高位是 00,所以不管是和左半部分做与运算还是与右半部分做与运算,最高位都是 00,所以直接把左右两边的贡献加起来即可。

FWT(A)1FWT(A)_1 比较特殊,他是右半部分,所以他与右半部分做与运算时,最高位多出来一个 11,而这是 FWT(A1)FWT(A_1) 没有考虑到的,如果多了个 11 的话,回看上面的定义,c(i&j)c(i\&j) 就会乘多一个 1-1,所以 FWT(A1)FWT(A_1) 的贡献是负的。而 FWT(A0)FWT(A_0) 无所谓,由于它本身就在左半部分,不会多出最高位的 11,所以贡献是正的。

求解证明

这里就用到异或了,以及用到了一个性质(描述有点长qwq,不想看的可以直接看下面的柿子):ii kk11 的数量的奇偶性异或 jj kk11 的数量的奇偶性等于 ii 异或 jj kk11 的数量的奇偶性相同。

用数学柿子来表示的话,设 P(i)P(i) 表示 ii 的奇偶性,当 ii 是奇数时 P(i)=1P(i)=1,当 ii 是偶数时 P(i)=0P(i)=0,那么这个性质就是:
P(c(i&k))ΛP(c(j&k))=P(c((iΛj)&k)) P(c(i\&k))\Lambda P(c(j\&k))=P(c((i\Lambda j)\&k))

为了严谨,证明还是要有的(其实随便手玩一下就懂了,很简单):

考虑 i,j,ki,j,k 的某一位,记为 x,y,zx,y,z(注意,是同一位),那么有两种情况(已经尽力压缩了qwq,怕多了各位不看呀,下面这些看起来繁琐的东西其实逻辑很简单的,静心细看就好):

  1. x,yx,y 都等于或不等于 zz(即 x=yx=y)。那么 i&ki\&kj&kj\&k 的这一位相同,所以 c(i&k)c(i\&k)c(j&k)c(j\&k) 同时 +1+1+0+0P(c(i&k))ΛP(c(j&k))P(c(i\&k))\Lambda P(c(j\&k)) 的值不变。再看等式右边,iΛji\Lambda j 的这一位为 00,那么 (iΛj)&k(i\Lambda j)\&k 的这一位就是 00,对 c((iΛj)&k)c((i\Lambda j)\&k) 没有贡献,所以 P(c((iΛj)&k))P(c((i\Lambda j)\&k)) 也不会变,等式成立。
  2. x,yx,y 一个等于 zz,一个不等于 zz(即 xyx\neq y)。假如 z=0z=0,那么参照情况 11,很容易知道此时两边又是不会发生变化的。如果 z=1z=1,此时 i&ki\&kj&kj\&k 的这一位不同,所以 c(i&k)c(i\&k)c(j&k)c(j\&k) 一个 +1+1,一个 +0+0,那么等式左边就会发生变化。而右边因为 iΛji\Lambda jkk 的这一位同时为 11,所以 c((iΛj)&k)c((i\Lambda j)\&k)+1+1,那么等式右边也会发生变化。等式两边同时变化,等式依然成立。

接下来就是求解的证明了,还是像上面那样大力展开(自己手(luan)推的,要是错了告知一下谢谢啦qwq):
FWT(C)[i]=j=02n1(1)c(i&j)C[j]=j=02n1(1)c(i&j)xΛy=jA[x]×B[y]=x=02n1y=02n1A[x]×B[y]×(1)c(i&(xΛy)) \begin{aligned} FWT(C)[i]&=\sum\limits_{j=0}^{2^n-1}(-1)^{c(i\&j)}C[j]\\ &=\sum\limits_{j=0}^{2^n-1}(-1)^{c(i\&j)}\sum_{x\Lambda y=j} A[x]\times B[y]\\ &=\sum_{x=0}^{2^n-1}\sum_{y=0}^{2^n-1}A[x]\times B[y]\times(-1)^{c(i\&(x\Lambda y))} \end{aligned}

诶!仔细观察发现,我们只在意 c(i&(xΛy))c(i\&(x\Lambda y)) 的奇偶性,因为他是 1-1 的指数,所以我们不妨在外面套一个 PP,显然是不影响答案的。
=x=02n1y=02n1A[x]×B[y]×(1)P(c(i&(xΛy))) =\sum_{x=0}^{2^n-1}\sum_{y=0}^{2^n-1}A[x]\times B[y]\times(-1)^{P(c(i\&(x\Lambda y)))}

这不就是上面性质里的样子吗!于是我们可以愉快的拆开啦:
=x=02n1y=02n1A[x]×B[y]×(1)P(c(i&x)) Λ P(c(i&y)) =\sum_{x=0}^{2^n-1}\sum_{y=0}^{2^n-1}A[x]\times B[y]\times(-1)^{P(c(i\&x))~\Lambda ~P(c(i\&y))}

仔细观察下面的柿子,是不是有什么规律?

(1)0Λ0=   1=(1)0+0(-1)^{0\Lambda 0}=~~~1=(-1)^{0+0}
(1)0Λ1=1=(1)0+1(-1)^{0\Lambda 1}=-1=(-1)^{0+1}
(1)1Λ0=1=(1)1+0(-1)^{1\Lambda 0}=-1=(-1)^{1+0}
(1)1Λ1=   1=(1)1+1(-1)^{1\Lambda 1}=~~~1=(-1)^{1+1}

没错!将指数里的 Λ\Lambda 换成 ++ 并不影响答案!

接下来就可以顺理成章的证明下去了。
=x=02n1y=02n1A[x]×B[y]×(1)P(c(i&x))+P(c(i&y))=x=02n1y=02n1A[x]×B[y]×(1)P(c(i&x))×(1)P(c(i&y))=x=02n1A[x]×(1)P(c(i&x))×y=02n1B[y]×(1)P(c(i&y))=FWT(A)[i]×FWT(B)[i] \begin{aligned} &=\sum_{x=0}^{2^n-1}\sum_{y=0}^{2^n-1}A[x]\times B[y]\times(-1)^{P(c(i\&x))+P(c(i\&y))}\\ &=\sum_{x=0}^{2^n-1}\sum_{y=0}^{2^n-1}A[x]\times B[y]\times(-1)^{P(c(i\&x))}\times(-1)^{P(c(i\&y))}\\ &=\sum_{x=0}^{2^n-1}A[x]\times (-1)^{P(c(i\&x))} \times \sum_{y=0}^{2^n-1}B[y]\times (-1)^{P(c(i\&y))}\\ &=FWT(A)[i]\times FWT(B)[i] \end{aligned}

逆变换

这个就稍微跟上面不一样的,不过一样的是都很容易理解~

A=FWT(A)A'=FWT(A),那么逆变换递推公式就是:
{(IFWT(A0)+IFWT(A1)2,IFWT(A0)IFWT(A1)2)(n>0)A(n=0) \begin{cases} (\frac {IFWT(A'_0)+IFWT(A'_1)} 2,\frac {IFWT(A'_0)-IFWT(A'_1)} 2) & (n>0)\\ A' & (n=0) \end{cases}

证明: 现在已知 FWT(A)0,FWT(A)1FWT(A)_0,FWT(A)_1,求 A0,A1A_0,A_1

FWT(A)0=FWT(A0)+FWT(A1),FWT(A)1=FWT(A0)FWT(A1)\because FWT(A)_0=FWT(A_0)+FWT(A_1),FWT(A)_1=FWT(A_0)-FWT(A_1)
FWT(A0)=FWT(A)0+FWT(A)12,FWT(A1)=FWT(A)0FWT(A)12\therefore FWT(A_0)=\frac {FWT(A)_0+FWT(A)_1} 2,FWT(A_1)=\frac {FWT(A)_0-FWT(A)_1} 2
A0=IFWT(FWT(A0))=IFWT(FWT(A)0+FWT(A)12)\therefore A_0=IFWT(FWT(A_0))=IFWT(\frac {FWT(A)_0+FWT(A)_1} 2)
    A1=IFWT(FWT(A1))=IFWT(FWT(A)0FWT(A)12)~~~~A_1=IFWT(FWT(A_1))=IFWT(\frac {FWT(A)_0-FWT(A)_1} 2)

代码实际上也没比上面的麻烦多少:

void FWT_xor(ll *f,int type)
{
	for(int mid=1;mid<n;mid<<=1)
	for(int block=mid<<1,j=0;j<n;j+=block)
	for(int i=j;i<j+mid;i++)//这三个循环与上面完全一样
	{
		ll x=f[i],y=f[i+mid];
		f[i]=(x+y)%mod*(type==1?1:inv_2)%mod;
		f[i+mid]=(x-y+mod)%mod*(type==1?1:inv_2)%mod;
	}
}

这几份代码都是在取模意义下的,如果不取模的话直接删掉 %mod 就好,然后除以 22 的部分直接除不需要逆元。

模板题

题目传送门

我真的觉得,我这个代码超级好看!(虽然有点不要脸qwq)

#include <cstdio>
#include <cstring>
#define ll long long
#define mod 998244353
#define maxn 1<<18

int n;
ll a[maxn],b[maxn],A[maxn],B[maxn];
void FWT_or(ll *f,int type)
{
	for(int mid=1;mid<n;mid<<=1)
	for(int block=mid<<1,j=0;j<n;j+=block)
	for(int i=j;i<j+mid;i++)
	f[i+mid]=(f[i+mid]+f[i]*type+mod)%mod;
}
void FWT_and(ll *f,int type)
{
	for(int mid=1;mid<n;mid<<=1)
	for(int block=mid<<1,j=0;j<n;j+=block)
	for(int i=j;i<j+mid;i++)
	f[i]=(f[i]+f[i+mid]*type+mod)%mod;
}
int inv_2=499122177;
void FWT_xor(ll *f,int type)
{
	for(int mid=1;mid<n;mid<<=1)
	for(int block=mid<<1,j=0;j<n;j+=block)
	for(int i=j;i<j+mid;i++)
	{
		ll x=f[i],y=f[i+mid];
		f[i]=(x+y)%mod*(type==1?1:inv_2)%mod;
		f[i+mid]=(x-y+mod)%mod*(type==1?1:inv_2)%mod;
	}
}
void work(void (*FWT)(ll *f,int type))//将函数作为参数传入
{
	for(int i=0;i<n;i++)a[i]=A[i],b[i]=B[i];
	FWT(a,1);FWT(b,1);
	for(int i=0;i<n;i++)a[i]=a[i]*b[i]%mod;
	FWT(a,-1);
	for(int i=0;i<n;i++)printf("%lld ",a[i]);
	printf("\n");
}

int main()
{
	scanf("%d",&n);n=1<<n;
	for(int i=0;i<n;i++)scanf("%lld",&A[i]),A[i]%=mod;
	for(int i=0;i<n;i++)scanf("%lld",&B[i]),B[i]%=mod;
	work(FWT_or);work(FWT_and);work(FWT_xor);
}
发布了276 篇原创文章 · 获赞 109 · 访问量 5万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览