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

〇、模板测试链接

传送门

壹、前言

对于多项式,我们有很多乱搞的卷积,我们用统一的形式:

\[h(n)=\sum_{i\psi j=n}f(i)g(j)\quad (i,j,n\in N) \]

来表示,其中 \(\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.或の正变换

要解决的问题是

\[h(n)=\sum_{i\cup j=n}f(i)g(j) \]

定义变换之后的函数有

\[f'(n)=\sum_{i\cup n=n}f(i) \]

即变换之后的函数的第 \(n\) 位是所有它的子集的原函数的求和.

然后,我们有

\[\begin{aligned} h'(n)&=\sum_{i\cup n=n}h(i) \\ &=\sum_{i\cup n=n}\sum_{x\cup y=i}f(x)g(y) \\ &=\sum_{x\cup n=n}f(x)\sum_{y\cup n=n}g(y) \\ &=f'(n)g'(n) \end{aligned} \]

满足我们的要求,也就是说,当 \(\psi\)\(\cup\) 时,有一个合法的 \(\tt FWT\) 变换是

\[f'(n)=\sum_{i\cup n=n}f(i) \]

这个变换应该怎么快速地实现呢?

对于函数 \(f\) ,假定其有 \(2^n\) 项,定义前 \(2^{n-1}\) 项是 \(f_0\),后 \(2^{n-1}\) 项是 \(f_1\),那么有

\[FWT(f)= \begin{cases} FWT(f_0),FWT(f_0)+FWT(f_1)&n\ge 1 \\ f&n=0 \end{cases} \]

含义就是去掉将后 \(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.或の逆变换

清楚正变换,逆变换就比较简单了,一定有

\[IFWT(f)= \begin{cases} IFWT(f_0),IFWT(f_1)-IFWT(f_0)&n\ge 1 \\ f&n=0 \end{cases} \]

肆、与(&)的变换

4.1.与の正变换

这个问题就是

\[h(n)=\sum_{i\cap j=n}f(i)g(j) \]

考虑定义变换之后的多项式满足

\[f'(n)=\sum_{i\cap n=n}f(i) \]

即对于变换之后的函数的第 \(n\) 位,为包含 \(n\) 这个集合的所有集合对应的原函数值之和.

那么,对于 \(h'(n)\)

\[\begin{aligned} h'(n)&=\sum_{i\cap n=n}h(i) \\ &=\sum_{i\cap n=n}\sum_{x\cap y=i}f(x)g(y) \\ &=\sum_{x\cap n=n}f(x)\sum_{y\cap n=n}g(y) \\ &=f'(n)g'(n) \end{aligned} \]

亦满足要求,所以对于 \(\cap\) 运算我们有合法的变换

\[f'(n)=\sum_{i\&n=n}f(i) \]

对于函数 \(f\) ,假定其有 \(2^n\) 项,定义前 \(2^{n-1}\) 项是 \(f_0\),后 \(2^{n-1}\) 项是 \(f_1\),那么有

\[FWT(f)= \begin{cases} FWT(f_0)+FWT(f_1),FWT(f_1)&n\ge 1 \\ f&n=0 \end{cases} \]

这个也比较好理解,即对于 \(i< 2^{n-1}\),一定有 \(i+2^{n-1}\) 属于 \(f_1\) 中的某些项,这些项包含了 \(i\) 这个集合,所以得加上它们.

4.2.与の逆变换

和或运算一样,反一下符号就 \(ok\) 了.

\[IFWT(f)= \begin{cases} IFWT(f_0)-IFWT(f_1),IFWT(f_1)&n\ge 1 \\ f&n=0 \end{cases} \]

伍、异或(^)的变换

5.1.异或の正变换

要解决

\[h(n)=\sum_{i\oplus j=n}f(i)g(j) \]

这个还有点难搞,我们先定义 \(cnt(i)\)\(i\) 在二进制下的 \(1\) 的个数,那么合法的变换应当是

\[f'(n)=\sum_{2\mid cnt(i\cap n)}f(i)-\sum_{2\nmid cnt(j\cap n)}f(j)=\sum_{i=0}^{n-1}(-1)^{cnt(i\cap n)}f(i) \]

怎么想到的我也不知道.此处使用 \(i,j\) 是为了好区分.

我们考虑证明它满足 \(h'(n)=f'(n)g'(n)\),对于 \(h'(n)\),我们有

\[\begin{aligned} h'(n) &=\sum_{2\mid cnt(i\cap n)}h(i)-\sum_{2\nmid cnt(i\cap n)}h(i) \\ &=\sum_{2\mid cnt(i\cap n)}\sum_{x_1\oplus y_1=i}f(x_1)g(y_1)-\sum_{2\nmid cnt(j\cap n)}\sum_{x_2\oplus y_2=j}f(x_2)g(y_2) \\ &=\sum_{2\mid cnt(i\cap n)}\sum_{x_1}f(x_1)g(i\oplus x_1)-\sum_{2\nmid cnt(j\cap n)}\sum_{x_2}f(x_2)g(j\oplus x_2) \\ &=\sum_{x_1}f(x_1)\sum_{2\mid cnt(i\cap n)}g(x_1\oplus i)-\sum_{x_2}f(x_2)\sum_{2\nmid cnt(j\cap n)}g(x_2\oplus j) \\ &=\sum_{x_1=0}^{x_1<n}f(x_1)\sum_{y_1=0}^{2\mid cnt((x_1\oplus y_1)\cap n),{y_1<n}}g(y_1)-\sum_{x_2=0}^{x_2<n}f(x_2)\sum_{y_2=0}^{2\nmid cnt((x_2\oplus y_2)\cap n),{y_2<n}}g(y_2) \\ &=\sum_{x=0}^{x<n}f(x)\sum_{y=0}^{y<n}(-1)^{cnt((x\oplus y)\cap n)}g(y) \end{aligned} \]

然后怎么推导?我们有这个结论:

\[cnt((x\oplus y)\cap n)=cnt(x\cap n)+cnt(y\cap n)-2\times cnt(x\cap y\cap n) \]

要证明它,只需证明 \(x,y,n\in [0,1]\) 成立即可推广.

然后我们发现,显然有 \(cnt((x\oplus y)\cap n)\)\(cnt(x\cap n)+cnt(y\cap n)\) 奇偶性相同,然后我们可以继续推到

\[\begin{aligned} h'(n)&=\sum_{x=0}^{x<n}f(x)\sum_{y=0}^{y<n}(-1)^{cnt((x\oplus y)\cap n)}g(y) \\ &=\sum_{x=0}^{x<n}f(x)\sum_{y=0}^{y<n}(-1)^{cnt(x\cap n)+cnt(y\cap n)}g(y) \\ &=\sum_{x=0}^{x<n}(-1)^{cnt(x\cap n)}f(x)\sum_{y=0}^{y<n}(-1)^{cnt(y\cap n)}g(y) \\ &=f'(n)g'(n) \end{aligned} \]

可以证明.

考虑如何快速实现这个过程:

对于函数 \(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\),对奇偶性不产生影响,那么有

\[FWT(f)= \begin{cases} FWT(f_0)+FWT(f_1),FWT(f_0)-FWT(f_1)&n\ge 1 \\ f&n=0 \end{cases} \]

5.2.异或の逆变换

这个还不简单?直接

\[IFWT(f)= \begin{cases} {IFWT(f_0)+IFWT(f_1)\over 2},{IFWT(f_0)-IFWT(f_1)\over 2}&n\ge 1 \\ f&n=0 \end{cases} \]

陆、一些小总结

6.0.关于说明

这些说明可能没有道理,只是能更好地记住公式而已......

6.1.或变换の说明

为什么或变换是

\[f'(n)=\sum_{i\cup n=n}f(i) \]

这个亚子的呢?

我们考虑对于 \(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是人想到的变换?(╯‵□′)╯︵┻━┻.

我试图去说明它为什么就是这样变的,我们先看这个变换本质是在干什么:

\[f'(n)=\sum_{2\mid cnt(i\cap n)}f(i)-\sum_{2\nmid cnt(j\cap n)}f(j) \]

就是将有偶数个相同元素的函数值减去有奇数个相同元素的函数值.

我们再观察我们要求的问题有什么特性:

\[h(n)=\sum_{i\oplus j=n}f(i)g(j) \]

不难发现,一定有 \(cnt(n)\equiv cnt(i)+cnt(j)\pmod 2\),而我们将最后的柿子带进去,观察可以获得什么:

\[\begin{aligned} h'(n)&=f'(n)g'(n) \\ &=\sum_{2\mid cnt(i\cap n)}f(i)\sum_{2\mid cnt(j\cap n)}g(j) + \sum_{2\nmid cnt(i\cap n)}f(i)\sum_{2\nmid cnt(j\cap n)}g(j) \\ &- \sum_{2\mid cnt(i\cap n)}f(i)\sum_{2\nmid cnt(j\cap n)}g(j) - \sum_{2\nmid cnt(i\cap n)}f(i)\sum_{2\mid cnt(j\cap n)}g(j) \end{aligned} \]

不难发现,两个带 \(+\) 的柿子,都有 \(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;
                }
            }
        }
    }
}
posted @ 2021-02-03 15:35  Arextre  阅读(160)  评论(0编辑  收藏  举报