FWT快速沃尔什变换学习笔记
1、FWT用来干啥啊
回忆一下多项式的卷积(C_k=sum_{i+j=k}A_i*B_j)
我们可以用(FFT)来做。
甚至在一些特殊情况下,我们(C_k=sum_{i*j=k}A_i*B_j)也能做(SDOI2015 序列统计)。
但是,如果我们把操作符换一下呢?
比如这样?
(C_k=sum_{i|j=k}A_i*B_j)
(C_k=sum_{i&j=k}A_i*B_j)
(C_k=sum_{iwedge j=k}A_i*B_j)
似乎这就不能用(FFT)来做了。
这样子就有了(FWT)——用来解决多项式的位运算卷积
2、FWT大概要怎么搞啊
我们想一想(FFT)在干啥?
先对于一个多项式求出他在若干个单位根的点值表示法
再将多项式乘起来,最后再复原。
那么,我们可不可以用一个类似的思路呢?
先将多项式求出另外一个多项式(FWT(A)),再将对应的位置乘起来,最后再复原?
也就是(FWT(C)=FWT(A)*FWT(B))(这个不是卷积,是对应位置相乘)?
废话,显然可以,要不然我还写什么(FWT)呢?
我们先来一点奇奇怪怪的记号吧。
因为多项式可以看成一个(n)维向量
所以,我们定义以下东西:
(A+B=(A_0+B_0,A_1+B_1,......))
(A-B=(A_0-B_0,A_1-B_1,......))
(Aoplus B=(sum_{ioplus j=0}A_i*B_j,sum_{ioplus j=1}A_i*B_j,......))
3、或(or)卷积
或卷积长成这个样子:(C_k=sum_{i|j=k}A_i*B_j)
写成向量的形式也就是这样子:(A|B=(sum_{i|j=0}A_i*B_j,sum_{i|j=1}A_i*B_j,......))
很显然的一点:这个东西满足交换律,也就是(A|B=B|A)
再来仔细的看看,这个东西也满足结合律。
简单的证明一下((A+B)|C=(sum_{i|j=0}(A_i+B_i)*C_j,......))
很明显可以把括号拆开,然后分成两个(sum),也就是(A|C+B|C)
我们这样子定义一下:
对于一个多项式(A)(最高次项是(2^n)),
我们把它分成两部分(A_0,A_1),分别表示前(2^{n-1})次项和后面的(2^{n-1})次项
也就是最高位为(0)与(1)的两部分。
对于或卷积,我们有:
对于(n=0)的时候,这个是非常显然的(常数还不显然了。。。)
啥?你问打个括号,然后中间一个逗号是啥意思?
不是说了这个结果是一个(2^n)维向量?
也就表示前(2^{n-1})项是逗号前面的东西,后面那几项是逗号后面的东西
完全可以理解为将两个多项式强行前后拼接成一个新的多项式。
好的,我们来伪证(感性理解)一下(n>0)的时候的式子
对于(A_0)中的任意一项,如果在做(or)卷积的时候,和任意一个(A_1)中的项(or)了一下
那么它的首位一定是(1),必定不会对于(FWT(A_0))产生任何贡献,
所以(FWT(A))的前(2^{n-1})项一定等于(FWT(A_0))。
后面这个东西看起来就很不好证明了,所以我们先考虑证明点别的东西。
(FWT(A+B)=FWT(A)+FWT(B))
证明(伪):
对于一个多项式(A)的(FWT(A)),它一定只是若干个原多项式中的若干项的若干倍的和。
即(FWT(A))一定不包含原多项式的某几项的乘积。
如果包含了原多项式的乘积,那么在求出卷积之后,
我们发现此时的结果与某个多项式自身的某两项的乘积有关
但是我们在或卷积的结果式中发现一定只与某个多项式的某一项与另一个多项式的某一项有关。
因此,我们知道(FWT(A))的任意一项只与(A)的某几项的和有关
因此,我们这个式子可以这样拆开。
此时,这个伪证成立。(这话怎么这么别扭)
但是怎么说,总是感觉证明后面都要贴上一个伪字,
如果我们能够知道(FWT(A))是个啥东西,我们就可以把这个字给扔掉了
先给出结论:
对于(or)卷积而言,(FWT(A)[i]=sum_{j|i=i}A[j])
它基于的原理呢?
如果(i|k=k,j|k=k),那么就有((i|j)|k=k)
这样说很不清楚,我们现在来证明后半部分为啥是(FWT(A_0+A_1))
因为(A_0)中取一项和(A_1)中取一项做(or)卷积,显然贡献会产生到(A_1)中去
首先,对于(A_1)中的任意两项的贡献,一定在(A_1)中,即使去掉了最高位,此时也会产生这部分的贡献
但是现在在合并(A_0)和(A_1)的贡献的时候,还需要考虑(A_0)的贡献
相当于只丢掉了最高位,因此,在(A_0)与(A_1)对应项的(FWT)的和就是我们现在合并之后的结果
所以也就是(FWT(A_0+A_1)=FWT(A_0)+FWT(A_1))
这样子,我们来考虑或卷积,也就是(FWT(A|B))
我们要证明它等于(FWT(A) imes FWT(B)),这样子我们就可以放心的使用(or)卷积了
证明:
这是一个数学归纳法的证明,请仔细看一看QwQ
当只有一项的时候这个是显然的。
好啦,这样就证明出了(or)卷积的正确性了
4、和(and)卷积
(and)卷积是这样的:(C_k=sum_{i&j=k}A_i*B_j)
写成向量的形式:(A&B=(sum_{i&j=0}A_i*B_j,sum_{i&j=1}A_i*B_j,......))
交换律?(A&B=B&A)显然成立
结合律?和前面一样是满足的。
好的,先把变换的式子写出来。
从某种意义上来说,(and)和(or)和很类似的。
我们这样看:
(0|0=0,0|1=1,1|0=1,1|1=1)
(0&0=0,0&1=0,1&0=0,1&1=1)
都是(3)个(0/1),然后剩下的那个只有一个
既然如此,其实我们也可以用(or)卷积类似的东西很容易的证明(and)卷积
(FWT(A+B)=FWT(A)+FWT(B))
大致的伪证就是(FWT(A))是关于(A)中元素的一个线性组合,显然满足分配率
接着只要再证明
(FWT(A) imes FWT(B)=FWT(A&B))就行了
方法仍然是数学归纳法。
证明:
好啦,这样子(and)卷积就证明完啦。
5、异或(xor)卷积
为了方便打,我就把异或操作用(oplus)来表示吧(而且这样似乎也是一种常用的表达方式)
主要原因是(wedge)太难打了
表达式:(C_k=sum_{ioplus j=k}A_i*B_j)
向量式按照上面写就行了
先写一下(FWT(A))吧
(FWT(A+B)=FWT(A)+FWT(B))
这个显然还是成立的,理由和上面是一样的。
接下来还是证明相同的东西
(FWT(A) imes FWT(B)=FWT(Aoplus B))
证明:
好啦好啦
这样子(xor)卷积也证明完啦。
于是我们可以开心的来写(FWT)啦
6、IFWT
我们现在可以在(O(nlogn))的时间复杂度里面得到(FWT(Aoplus B)),其中(oplus)表示一个位运算。
得到了(FWT)之后我们需要还原这个数组,也就是(IFWT)(叫(UFWT)也没啥问题??)
怎么求?
正向的过程我们知道了,逆向的反着做啊。
所以:
(or)卷积:$$IFWT(A)=(IFWT(A_0),IFWT(A_1)-IFWT(A_0))$$
(and)卷积:$$IFWT(A)=(IFWT(A_0)-IFWT(A_1),IFWT(A_1))$$
(xor)卷积:$$IFWT(A)=(frac{IFWT(A_0)+IFWT(A_1)}{2},frac{IFWT(A_0)-IFWT(A_1)}{2})$$
7、代码实现
(or)卷积的代码
void FWT(ll *P,int opt)
{
for(int i=2;i<=N;i<<=1)
for(int p=i>>1,j=0;j<N;j+=i)
for(int k=j;k<j+p;++k)
P[k+p]+=P[k]*opt;
}
(and)卷积只需要在(or)卷积的基础上修改一点点就好了
void FWT(ll *P,int opt)
{
for(int i=2;i<=N;i<<=1)
for(int p=i>>1,j=0;j<N;j+=i)
for(int k=j;k<j+p;++k)
P[k]+=P[k+p]*opt;
}
(xor)卷积其实也差不多(这个是在模意义下的(FWT))
如果不是在模意义下的话,开一个(long long),然后把逆元变成直接除二就好了。
void FWT(int *P,int opt)
{
for(int i=2;i<=N;i<<=1)
for(int p=i>>1,j=0;j<N;j+=i)
for(int k=j;k<j+p;++k)
{
int x=P[k],y=P[k+p];
P[k]=(x+y)%MOD;P[k+p]=(x-y+MOD)%MOD;
if(opt==-1)P[k]=1ll*P[k]*inv2%MOD,P[k+p]=1ll*P[k+p]*inv2%MOD;
}
}
Upd:
写了个好看点的板子,这样就和(FFT)长得很像了。
void FWT_or(int *a,int opt)
{
for(int i=1;i<N;i<<=1)
for(int p=i<<1,j=0;j<N;j+=p)
for(int k=0;k<i;++k)
if(opt==1)a[i+j+k]=(a[j+k]+a[i+j+k])%MOD;
else a[i+j+k]=(a[i+j+k]+MOD-a[j+k])%MOD;
}
void FWT_and(int *a,int opt)
{
for(int i=1;i<N;i<<=1)
for(int p=i<<1,j=0;j<N;j+=p)
for(int k=0;k<i;++k)
if(opt==1)a[j+k]=(a[j+k]+a[i+j+k])%MOD;
else a[j+k]=(a[j+k]+MOD-a[i+j+k])%MOD;
}
void FWT_xor(int *a,int opt)
{
for(int i=1;i<N;i<<=1)
for(int p=i<<1,j=0;j<N;j+=p)
for(int k=0;k<i;++k)
{
int X=a[j+k],Y=a[i+j+k];
a[j+k]=(X+Y)%MOD;a[i+j+k]=(X+MOD-Y)%MOD;
if(opt==-1)a[j+k]=1ll*a[j+k]*inv2%MOD,a[i+j+k]=1ll*a[i+j+k]*inv2%MOD;
}
}