[学习笔记]FWT快速沃尔什变换
〇、模板测试链接
壹、前言
对于多项式,我们有很多乱搞的卷积,我们用统一的形式:
来表示,其中 \(\psi\) 可以是任意运算符.
众所周知,当 \(\psi\) 为 $\times $ 时就是迪利克雷卷积(\(*\)),这个运算在杜教筛中被使用;当 \(\psi\) 是 \(+\) 时就是一般的多项式卷积,可以使用 \(\tt NTT\) 或者 \(\tt FFT\) 在 \(\mathcal O(n\log n)\) 的时间内解决;而 \(\tt FWT\) 是解决当 \(\psi\) 是位运算的 \(|,\&,\wedge\) 的情况(这个 \(\wedge\) 是异或运算,尖尖打不出来......)的情况,他的复杂度也是 \(\mathcal O(n\log n)\) 的.
下文所说的集合等都是在二进制意义下的,并且将或写作 \(\cup\),将与写作 \(\cap\),将异或写作 \(\oplus\).
贰、大致思想
大致思想其实和 \(\tt FFT\) 较为类似,\(\tt FFT\) 是对于 \(h(n)=\sum_{i=1}^nf(i)g(n-i)\) 中的 \(h,f,g\) 变换为点值表达式 \(h',f',g'\),然后有 \(h’(n)=f'(n)g'(n)\),最后再将 \(h'\) 变换为 \(h\) 得到 \(f,g\) 的卷积.
而 \(\tt FWT\) 是对于 \(h(n)=\sum_{i\psi j =n}f(i)g(j)\),将 \(h,f,g\) 变换为某种形式,使得 \(h'(n)=f'(n)g'(n)\),并且变换可逆.
叁、或(|)的变换
3.1.或の正变换
要解决的问题是
定义变换之后的函数有
即变换之后的函数的第 \(n\) 位是所有它的子集的原函数的求和.
然后,我们有
满足我们的要求,也就是说,当 \(\psi\) 为 \(\cup\) 时,有一个合法的 \(\tt FWT\) 变换是
这个变换应该怎么快速地实现呢?
对于函数 \(f\) ,假定其有 \(2^n\) 项,定义前 \(2^{n-1}\) 项是 \(f_0\),后 \(2^{n-1}\) 项是 \(f_1\),那么有
含义就是去掉将后 \(2^{n-1}\) 去掉二进制下第 \(n\) 位(最高位)之后,其实就是有 \(2^{n-1}\) 项的子变换,但是对于 \(i<2^{n-1}\) 的部分,\(i+2^{n-1}\) 一定会对应后 \(2^{n-1}\) 的某些项中,对于这些项来说,\(i\) 是他们的子集,所以还得加上 \(FWT(f_0)\),这个复杂度是 \(\mathcal O(n\log n)\) 的
边界情况就不用说明了.
注意变换的时候从下到上.
3.2.或の逆变换
清楚正变换,逆变换就比较简单了,一定有
肆、与(&)的变换
4.1.与の正变换
这个问题就是
考虑定义变换之后的多项式满足
即对于变换之后的函数的第 \(n\) 位,为包含 \(n\) 这个集合的所有集合对应的原函数值之和.
那么,对于 \(h'(n)\) 有
亦满足要求,所以对于 \(\cap\) 运算我们有合法的变换
对于函数 \(f\) ,假定其有 \(2^n\) 项,定义前 \(2^{n-1}\) 项是 \(f_0\),后 \(2^{n-1}\) 项是 \(f_1\),那么有
这个也比较好理解,即对于 \(i< 2^{n-1}\),一定有 \(i+2^{n-1}\) 属于 \(f_1\) 中的某些项,这些项包含了 \(i\) 这个集合,所以得加上它们.
4.2.与の逆变换
和或运算一样,反一下符号就 \(ok\) 了.
伍、异或(^)的变换
5.1.异或の正变换
要解决
这个还有点难搞,我们先定义 \(cnt(i)\) 为 \(i\) 在二进制下的 \(1\) 的个数,那么合法的变换应当是
怎么想到的我也不知道.此处使用 \(i,j\) 是为了好区分.
我们考虑证明它满足 \(h'(n)=f'(n)g'(n)\),对于 \(h'(n)\),我们有
然后怎么推导?我们有这个结论:
要证明它,只需证明 \(x,y,n\in [0,1]\) 成立即可推广.
然后我们发现,显然有 \(cnt((x\oplus y)\cap n)\) 和 \(cnt(x\cap n)+cnt(y\cap n)\) 奇偶性相同,然后我们可以继续推到
可以证明.
考虑如何快速实现这个过程:
对于函数 \(f\) ,假定其有 \(2^n\) 项,定义前 \(2^{n-1}\) 项是 \(f_0\),后 \(2^{n-1}\) 项是 \(f_1\),不难发现,前 \(2^{n-1}\) 项与后 \(2^{n-1}\) 本质差别就是二进制下最高位多了个 \(1\),对于后 \(2^{n-1}\) 个数来说,由于是取 \(\cap\),则这个 \(1\) 会让奇偶性取反,相应地,符号也会取反了,对于前 \(2^{n-1}\) 个数来说,由于他们的最高位没有 \(1\),在取 \(\cap\) 之后依然没有 \(1\),对奇偶性不产生影响,那么有
5.2.异或の逆变换
这个还不简单?直接
陆、一些小总结
6.0.关于说明
这些说明可能没有道理,只是能更好地记住公式而已......
6.1.或变换の说明
为什么或变换是
这个亚子的呢?
我们考虑对于 \(h(n)\),首先有 \(h(n)=\sum_{i|j=n}f(i)g(j)\),而在什么时候会有对应的 \(h(i),g(j)\) 为 \(h(n)\) 提供贡献?应当是 \(i,j\) 都是 \(n\) 的子集的情况,所以我们的变换定义对于 \(f'(i)\),它的值是所有它的子集的原函数值之和,这就和我们求的对应上来.
6.2.与变换の说明
与变换和或变换也差不多了,我们考虑对于 \(h(n)=\sum_{i\cap j=n}f(i)g(j)\),当 \(i,j\) 都包含 \(n\),也就是 \(n\) 是 \(i,j\) 的子集时,\(h(i),g(j)\) 会给 \(h(n)\) 提供贡献,故而我们定义变换之后的 \(f'(i)\) 为所有包含它的集合的原函数值之和.
6.3.异或变换の说明
这TM是人想到的变换?(╯‵□′)╯︵┻━┻.
我试图去说明它为什么就是这样变的,我们先看这个变换本质是在干什么:
就是将有偶数个相同元素的函数值减去有奇数个相同元素的函数值.
我们再观察我们要求的问题有什么特性:
不难发现,一定有 \(cnt(n)\equiv cnt(i)+cnt(j)\pmod 2\),而我们将最后的柿子带进去,观察可以获得什么:
不难发现,两个带 \(+\) 的柿子,都有 \(cnt(n)\equiv cnt(i)+cnt(j)\pmod 2\),对于两个 \(-\) 的柿子,都有 \(cnt(n)\not\equiv cnt(i)+cnt(j)\pmod 2\),即我们的本质是加上所有 \(1\) 的个数奇偶性相同的,去掉 \(1\) 的个数奇偶性相异的.
柒、码!
尝试着写成和 \(\tt NTT\) 差不多的形式.
inline void fwt_or(int* f,const int n,const int opt=1){
for(int p=2;p<=n;p<<=1){
int len=p>>1;
for(int k=0;k<n;k+=p){
for(int i=k;i<k+len;++i)
f[i+len]=(f[i+len]+opt*f[i])%mod;
}
}
}
inline void fwt_and(int* f,const int n,const int opt=1){
for(int p=2;p<=n;p<<=1){
int len=p>>1;
for(int k=0;k<n;k+=p){
for(int i=k;i<k+len;++i)
f[i]=(f[i]+opt*f[i+len])%mod;
}
}
}
inline void fwt_xor(int* f,const int n,const int opt=1){
for(int p=2;p<=n;p<<=1){
int len=p>>1,x,y;
for(int k=0;k<n;k+=p){
for(int i=k;i<k+len;++i){
x=f[i],y=f[i+len];
f[i]=(x+y)%mod,f[i+len]=(x-y)%mod;
if(opt==-1){
f[i]=1ll*f[i]*inv2%mod;
f[i+len]=1ll*f[i+len]*inv2%mod;
}
}
}
}
}