FWT快速沃尔什变换学习笔记

FWT快速沃尔什变换学习笔记

1、FWT用来干啥啊

回忆一下多项式的卷积Ck=i+j=kAiBj

我们可以用FFT来做。

甚至在一些特殊情况下,我们Ck=ij=kAiBj也能做(SDOI2015 序列统计)。

但是,如果我们把操作符换一下呢?

比如这样?

Ck=i|j=kAiBj

Ck=i&j=kAiBj

Ck=ij=kAiBj

似乎这就不能用FFT来做了。

这样子就有了FWT——用来解决多项式的位运算卷积

2、FWT大概要怎么搞啊

我们想一想FFT在干啥?

先对于一个多项式求出他在若干个单位根的点值表示法

再将多项式乘起来,最后再复原。

那么,我们可不可以用一个类似的思路呢?

先将多项式求出另外一个多项式FWT(A),再将对应的位置乘起来,最后再复原?

也就是FWT(C)=FWT(A)FWT(B)(这个不是卷积,是对应位置相乘)?

废话,显然可以,要不然我还写什么FWT呢?

我们先来一点奇奇怪怪的记号吧。

因为多项式可以看成一个n维向量

所以,我们定义以下东西:

A+B=(A0+B0,A1+B1,......)

AB=(A0B0,A1B1,......)

AB=(ij=0AiBj,ij=1AiBj,......)

3、或(or)卷积

或卷积长成这个样子:Ck=i|j=kAiBj

写成向量的形式也就是这样子:A|B=(i|j=0AiBj,i|j=1AiBj,......)

很显然的一点:这个东西满足交换律,也就是A|B=B|A

再来仔细的看看,这个东西也满足结合律。

简单的证明一下(A+B)|C=(i|j=0(Ai+Bi)Cj,......)

很明显可以把括号拆开,然后分成两个,也就是A|C+B|C

我们这样子定义一下:

对于一个多项式A(最高次项是2n),

我们把它分成两部分A0,A1,分别表示前2n1次项和后面的2n1次项

也就是最高位为01的两部分。

对于或卷积,我们有:

FWT(A)={(FWT(A0),FWT(A0+A1))n>0An=0

对于n=0的时候,这个是非常显然的(常数还不显然了。。。)

啥?你问打个括号,然后中间一个逗号是啥意思?

不是说了这个结果是一个2n维向量?

也就表示前2n1项是逗号前面的东西,后面那几项是逗号后面的东西

完全可以理解为将两个多项式强行前后拼接成一个新的多项式。

好的,我们来伪证(感性理解)一下n>0的时候的式子

对于A0中的任意一项,如果在做or卷积的时候,和任意一个A1中的项or了一下

那么它的首位一定是1,必定不会对于FWT(A0)产生任何贡献,

所以FWT(A)的前2n1项一定等于FWT(A0)

后面这个东西看起来就很不好证明了,所以我们先考虑证明点别的东西。

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

证明(伪):

对于一个多项式AFWT(A),它一定只是若干个原多项式中的若干项的若干倍的和。

FWT(A)一定不包含原多项式的某几项的乘积。

如果包含了原多项式的乘积,那么在求出卷积之后,

我们发现此时的结果与某个多项式自身的某两项的乘积有关

但是我们在或卷积的结果式中发现一定只与某个多项式的某一项与另一个多项式的某一项有关。

因此,我们知道FWT(A)的任意一项只与A的某几项的和有关

因此,我们这个式子可以这样拆开。

此时,这个伪证成立。(这话怎么这么别扭)

但是怎么说,总是感觉证明后面都要贴上一个伪字,

如果我们能够知道FWT(A)是个啥东西,我们就可以把这个字给扔掉了

先给出结论:

对于or卷积而言,FWT(A)[i]=j|i=iA[j]

它基于的原理呢?

如果i|k=k,j|k=k,那么就有(i|j)|k=k

这样说很不清楚,我们现在来证明后半部分为啥是FWT(A0+A1)

因为A0中取一项和A1中取一项做or卷积,显然贡献会产生到A1中去

首先,对于A1中的任意两项的贡献,一定在A1中,即使去掉了最高位,此时也会产生这部分的贡献

但是现在在合并A0A1的贡献的时候,还需要考虑A0的贡献

相当于只丢掉了最高位,因此,在A0A1对应项的FWT的和就是我们现在合并之后的结果

所以也就是FWT(A0+A1)=FWT(A0)+FWT(A1)

这样子,我们来考虑或卷积,也就是FWT(A|B)

我们要证明它等于FWT(A)×FWT(B),这样子我们就可以放心的使用or卷积了

证明:

FWT(A|B)=FWT((A|B)0,(A|B)1)=FWT(A0|B0,A0|B1+A1|B0+A1|B1)=(FWT(A0|B0),FWT(A0|B0+A0|B1+A1|B0+A1|B1))=(FWT(A0)×FWT(B0),FWT(A0)×FWT(B0)+FWT(A0)×FWT(B1)+FWT(A1)×FWT(B0)+FWT(A1)×FWT(B1))=(FWT(A0)×FWT(B0),(FWT(A0)+FWT(A1))×(FWT(B0)+FWT(B1)))=(FWT(A0),FWT(A0+A1))×(FWT(B0),FWT(B0+B1))=FWT(A)×FWT(B)

这是一个数学归纳法的证明,请仔细看一看QwQ

当只有一项的时候这个是显然的。

好啦,这样就证明出了or卷积的正确性了

4、和(and)卷积

and卷积是这样的:Ck=i&j=kAiBj

写成向量的形式:A&B=(i&j=0AiBj,i&j=1AiBj,......)

交换律?A&B=B&A显然成立

结合律?和前面一样是满足的。

好的,先把变换的式子写出来。

FWT(A)={(FWT(A0+A1),FWT(A1))n>0An=0

从某种意义上来说,andor和很类似的。

我们这样看:

0|0=0,0|1=1,1|0=1,1|1=1

0&0=0,0&1=0,1&0=0,1&1=1

都是30/1,然后剩下的那个只有一个

既然如此,其实我们也可以用or卷积类似的东西很容易的证明and卷积

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

大致的伪证就是FWT(A)是关于A中元素的一个线性组合,显然满足分配率

接着只要再证明

FWT(A)×FWT(B)=FWT(A&B)就行了

方法仍然是数学归纳法。

证明:

FWT(A&B)=FWT((A&B)0,(A&B)1)=FWT(A0&B0+A0&B1+A1&B0,A1&B1)=(FWT(A0&B0+A0&B1+A1&B0+A1&B1),FWT(A1&B1))=((FWT(A0)+FWT(A1))×(FWT(B0)+FWT(B1)),FWT(A1)FWT(B1))=(FWT(A0+A1),FWT(A1))×(FWT(B0+B1),FWT(B1))=FWT(A)×FWT(B)

好啦,这样子and卷积就证明完啦。

5、异或(xor)卷积

为了方便打,我就把异或操作用来表示吧(而且这样似乎也是一种常用的表达方式)

主要原因是太难打了

表达式:Ck=ij=kAiBj

向量式按照上面写就行了

先写一下FWT(A)

FWT(A)={(FWT(A0)+FWT(A1),FWT(A0)FWT(A1))n>0An=0

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

这个显然还是成立的,理由和上面是一样的。

接下来还是证明相同的东西

FWT(A)×FWT(B)=FWT(AB)

证明:

FWT(AB)=(FWT(AB)0+FWT(AB)1,FWT(AB)0FWT(AB)1)=(FWT(A0B0+A1B1+A1B0+A0B1),FWT(A0B0+A1B1A1B0A0B1))=((FWT(A0)+FWT(A1))×(FWT(B0)+FWT(B1)),(FWT(A0)FWT(A1))×(FWT(B0)FWT(B1))=(FWT(A0+A1)×(B0+B1),FWT(A0A1)×FWT(B0B1))=(FWT(A0+A1),FWT(A0A1))×(FWT(B0+B1),FWT(B0B1))=FWT(A)×FWT(B)

好啦好啦

这样子xor卷积也证明完啦。

于是我们可以开心的来写FWT

6、IFWT

我们现在可以在O(nlogn)的时间复杂度里面得到FWT(AB),其中表示一个位运算。

得到了FWT之后我们需要还原这个数组,也就是IFWT(叫UFWT也没啥问题??)

怎么求?

正向的过程我们知道了,逆向的反着做啊。

所以:

or卷积:IFWT(A)=(IFWT(A0),IFWT(A1)IFWT(A0))

and卷积:IFWT(A)=(IFWT(A0)IFWT(A1),IFWT(A1))

xor卷积:IFWT(A)=(IFWT(A0)+IFWT(A1)2,IFWT(A0)IFWT(A1)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;
            }
}
posted @   小蒟蒻yyb  阅读(24530)  评论(44编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示