回忆一下多项式的卷积$C_k=\sum_{i+j=k}A_i*B_j$数组
咱们能够用$FFT$来作。学习
甚至在一些特殊状况下,咱们$C_k=\sum_{ij=k}A_iB_j$也能作(SDOI2015 序列统计)。spa
可是,若是咱们把操做符换一下呢?code
好比这样?数学
\(C_k=\sum_{i|j=k}A_i*B_j\)io
\(C_k=\sum_{i\&j=k}A_i*B_j\)class
\(C_k=\sum_{i\wedge j=k}A_i*B_j\)基础
彷佛这就不能用$FFT$来作了。原理
这样子就有了$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,......)\)
\(A\oplus B=(\sum_{i\oplus j=0}A_i*B_j,\sum_{i\oplus j=1}A_i*B_j,......)\)
或卷积长成这个样子:\(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^$次项和后面的$2^$次项
也就是最高位为$0$与$1$的两部分。
对于或卷积,咱们有:
\(FWT(A)=\begin{cases}(FWT(A_0),FWT(A_0+A_1)) & n\gt0 \\ A & n=0\end{cases}\)
对于$n=0$的时候,这个是很是显然的(常数还不显然了。。。)
啥?你问打个括号,而后中间一个逗号是啥意思?
不是说了这个结果是一个$2^n$维向量?
也就表示前$2^$项是逗号前面的东西,后面那几项是逗号后面的东西
彻底能够理解为将两个多项式强行先后拼接成一个新的多项式。
好的,咱们来伪证(感性理解)一下$n>0$的时候的式子
对于$A_0$中的任意一项,若是在作$or$卷积的时候,和任意一个$A_1$中的项$or$了一下
那么它的首位必定是$1$,一定不会对于$FWT(A_0)$产生任何贡献,
因此$FWT(A)$的前$2^$项必定等于$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)\times FWT(B)$,这样子咱们就能够放心的使用$or$卷积了
证实:
这是一个数学概括法的证实,请仔细看一看QwQ
当只有一项的时候这个是显然的。
好啦,这样就证实出了$or$卷积的正确性了
$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$显然成立
结合律?和前面同样是知足的。
好的,先把变换的式子写出来。
\(FWT(A)=\begin{cases}(FWT(A_0+A_1),FWT(A_1)) & n\gt0 \\ A & n=0\end{cases}\)
从某种意义上来讲,$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)\times FWT(B)=FWT(A&B)$就好了
方法仍然是数学概括法。
证实:
好啦,这样子$and$卷积就证实完啦。
为了方便打,我就把异或操做用$\oplus$来表示吧(并且这样彷佛也是一种经常使用的表达方式)
主要缘由是$\wedge$太难打了
表达式:\(C_k=\sum_{i\oplus j=k}A_i*B_j\)
向量式按照上面写就好了
先写一下$FWT(A)$吧 \(FWT(A)=\begin{cases}(FWT(A_0)+FWT(A_1),FWT(A_0)-FWT(A_1)) & n>0\\A & n=0\end{cases}\)
\(FWT(A+B)=FWT(A)+FWT(B)\)
这个显然仍是成立的,理由和上面是同样的。
接下来仍是证实相同的东西
\(FWT(A)\times FWT(B)=FWT(A\oplus B)\)
证实:
好啦好啦
这样子$xor$卷积也证实完啦。
因而咱们能够开心的来写$FWT$啦
咱们如今能够在$O(nlogn)$的时间复杂度里面获得$FWT(A\oplus 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})\)
$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; } }