FWT快速沃尔什变换学习笔记
FWT快速沃尔什变换学习笔记
1、FWT用来干啥啊
回忆一下多项式的卷积
我们可以用来做。
甚至在一些特殊情况下,我们也能做(SDOI2015 序列统计)。
但是,如果我们把操作符换一下呢?
比如这样?
似乎这就不能用来做了。
这样子就有了——用来解决多项式的位运算卷积
2、FWT大概要怎么搞啊
我们想一想在干啥?
先对于一个多项式求出他在若干个单位根的点值表示法
再将多项式乘起来,最后再复原。
那么,我们可不可以用一个类似的思路呢?
先将多项式求出另外一个多项式,再将对应的位置乘起来,最后再复原?
也就是(这个不是卷积,是对应位置相乘)?
废话,显然可以,要不然我还写什么呢?
我们先来一点奇奇怪怪的记号吧。
因为多项式可以看成一个维向量
所以,我们定义以下东西:
3、或(or)卷积
或卷积长成这个样子:
写成向量的形式也就是这样子:
很显然的一点:这个东西满足交换律,也就是
再来仔细的看看,这个东西也满足结合律。
简单的证明一下
很明显可以把括号拆开,然后分成两个,也就是
我们这样子定义一下:
对于一个多项式(最高次项是),
我们把它分成两部分,分别表示前次项和后面的次项
也就是最高位为与的两部分。
对于或卷积,我们有:
对于的时候,这个是非常显然的(常数还不显然了。。。)
啥?你问打个括号,然后中间一个逗号是啥意思?
不是说了这个结果是一个维向量?
也就表示前项是逗号前面的东西,后面那几项是逗号后面的东西
完全可以理解为将两个多项式强行前后拼接成一个新的多项式。
好的,我们来伪证(感性理解)一下的时候的式子
对于中的任意一项,如果在做卷积的时候,和任意一个中的项了一下
那么它的首位一定是,必定不会对于产生任何贡献,
所以的前项一定等于。
后面这个东西看起来就很不好证明了,所以我们先考虑证明点别的东西。
证明(伪):
对于一个多项式的,它一定只是若干个原多项式中的若干项的若干倍的和。
即一定不包含原多项式的某几项的乘积。
如果包含了原多项式的乘积,那么在求出卷积之后,
我们发现此时的结果与某个多项式自身的某两项的乘积有关
但是我们在或卷积的结果式中发现一定只与某个多项式的某一项与另一个多项式的某一项有关。
因此,我们知道的任意一项只与的某几项的和有关
因此,我们这个式子可以这样拆开。
此时,这个伪证成立。(这话怎么这么别扭)
但是怎么说,总是感觉证明后面都要贴上一个伪字,
如果我们能够知道是个啥东西,我们就可以把这个字给扔掉了
先给出结论:
对于卷积而言,
它基于的原理呢?
如果,那么就有
这样说很不清楚,我们现在来证明后半部分为啥是
因为中取一项和中取一项做卷积,显然贡献会产生到中去
首先,对于中的任意两项的贡献,一定在中,即使去掉了最高位,此时也会产生这部分的贡献
但是现在在合并和的贡献的时候,还需要考虑的贡献
相当于只丢掉了最高位,因此,在与对应项的的和就是我们现在合并之后的结果
所以也就是
这样子,我们来考虑或卷积,也就是
我们要证明它等于,这样子我们就可以放心的使用卷积了
证明:
这是一个数学归纳法的证明,请仔细看一看QwQ
当只有一项的时候这个是显然的。
好啦,这样就证明出了卷积的正确性了
4、和(and)卷积
卷积是这样的:
写成向量的形式:
交换律?显然成立
结合律?和前面一样是满足的。
好的,先把变换的式子写出来。
从某种意义上来说,和和很类似的。
我们这样看:
都是个,然后剩下的那个只有一个
既然如此,其实我们也可以用卷积类似的东西很容易的证明卷积
大致的伪证就是是关于中元素的一个线性组合,显然满足分配率
接着只要再证明
就行了
方法仍然是数学归纳法。
证明:
好啦,这样子卷积就证明完啦。
5、异或(xor)卷积
为了方便打,我就把异或操作用来表示吧(而且这样似乎也是一种常用的表达方式)
主要原因是太难打了
表达式:
向量式按照上面写就行了
先写一下吧
这个显然还是成立的,理由和上面是一样的。
接下来还是证明相同的东西
证明:
好啦好啦
这样子卷积也证明完啦。
于是我们可以开心的来写啦
6、IFWT
我们现在可以在的时间复杂度里面得到,其中表示一个位运算。
得到了之后我们需要还原这个数组,也就是(叫也没啥问题??)
怎么求?
正向的过程我们知道了,逆向的反着做啊。
所以:
卷积:
卷积:
卷积:
7、代码实现
卷积的代码
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;
}
卷积只需要在卷积的基础上修改一点点就好了
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;
}
卷积其实也差不多(这个是在模意义下的)
如果不是在模意义下的话,开一个,然后把逆元变成直接除二就好了。
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:
写了个好看点的板子,这样就和长得很像了。
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;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· 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工具