FWT(快速沃尔什变换)

FWT(快速沃尔什变换)

前言

萌新刚学多项式 1ms,有误或者不严谨指出欢迎指出,感谢大佬!

update 2025.10.25 这是我,第三次学习 FWT 了吧。应该这次能学会吧?修改了之前蒙蒙的时候写的东西。

参考

OI Wiki

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

题解 P4717 【【模板】快速沃尔什变换】

位运算卷积(FWT) & 集合幂级数

简介

使用类似 FFT 的方法求位运算卷积。

我们熟知的多项式卷积是求 \(\sum_{i=1}^n a_i b_{n-i}\)

对于位运算 \(\oplus\),可以是 or,and,xor。其卷积大概是 \(\sum_{i=1}^n \sum_{i \oplus j = n} a_i b_j\)

比如或卷积是 \(\sum_{i=1}^n \sum_{i|j = n} a_i b_j\),也就是我们常说的枚举子集。

对于 \(c_n = \sum_{i=1}^n \sum_{i \oplus j = n} a_i b_j\),我们要求出 \(c_n\) 或者 \(\{c_i\}\),最朴素的暴力枚举 \(i,j\) 时间复杂度是 \(O(n^2)\) 的。考虑使用 FWT 优化到 \(O(n \log n)\)

算法流程

引入

记对序列 \(A=\{a_i\}\) 进行 FWT 变换后的序列(形式幂级数)是 \(fwt(A)\)

\(A,B,C\) 均指序列,\(a_i\) 之类指序列的第 \(i\) 项(从 \(0\) 开始记下标)。\(fwt(A)\) 之类是序列,\(fwt(A)_i\) 之类是指序列的第 \(i\) 项(同样从 \(0\) 开始记下标)。

我们要构造 \(A \oplus B = C \Longleftrightarrow fwt(A) \cdot fwt(B) = fwt(C)\)。需要在 \(O(n \log n)\) 的时间复杂度内计算 \(A \to fwt(A)\)\(fwt(A)\to A\),和线性求出 \(fwt(A) \cdot fwt(B) = fwt(C)\)

可以类似 FFT,将 \(A,B,C\) 理解为多项式(系数),那么 \(fwt(A),fwt(B),fwt(C)\) 就是点值。

其中 \(A \oplus B = C\)\(c_x = \sum_{i \oplus j = x} a_i b_j\),即位运算卷积。

\(fwt(A) \cdot fwt(B) = fwt(C)\)\(fwt(C)_i = fwt(A)_i fwt(B)_i\),即点值相乘。

\([i,j]\) 表示 \(a_j\) 是否对 \(fwt(A)_i\) 贡献。有 \([i,j] \in \{0,1\}\)?(其实不一定)

\(fwt(A)_i=\sum_{j=0}^{n-1} [i,j] a_j\)

这个操作就类似于将 \(i\) 代入 \(A\) 得到的点值就是对应项的值乘以对应项的贡献系数相加。因为是位运算,所以贡献系数只有 \(0,1\)(其实不一定),而不是像一般多项式是 \(x^k\)

求 FWT

我们需要知道 \([i,j]\) 是什么,即如何构造 \(fwt(A)\)

\[\begin{aligned} fwt(A)_i \cdot fwt(B)_i & = fwt(C)_i\\ \sum_{j=0}^{n-1} [i,j] a_j \sum_{k=0}^{n-1} [i,k] b_k & = \sum_{p=0}^{n-1} [i,p] c_p\\ \sum_{j=0}^{n-1} \sum_{k=0}^{n-1} [i,j] [i,k] a_j b_k & = \sum_{p=0}^{n-1} [i,p] c_p\\ \end{aligned} \]

由于 \(A \oplus B = C\),我们有 \(c_n = \sum_{i \oplus j = i} a_i b_j\)

因此我们只需要保证对于所有的 \([i,p]=1\) 当且仅当 \(j \oplus k = p\) 的时候 \([i,j]=[i,k]=1\)

因为是位运算,我们只需要分别考虑每一位,然后把每一位的贡献系数乘起来。即考虑 \([0,0],[0,1],[1,0],[1,1]\) 的取值。每种位运算都是不同的取值。

即(\(i_d,j_d\) 表示 \(i,j\) 二进制下从高数第 \(d\) 位):

\[fwt(A)_i=\sum_{j=0}^{n-1} (\prod_{d} [i_d,j_d]) a_j \]


如何快速根据 \(A\)\(fwt(A)\)

直接算是 \(O(n^2)\) 的。

考虑按照 \(j\) 的最高位分开算。其中 \(i_0\)\(i\) 从高数的第 \(0\) 位。\(i',j'\) 表示原数去掉最高位(含前导零)。\(A_0,A_1\) 表示序列 \(A\) 的前/后半部分。

\[\begin{aligned} fwt(A)_i & = [i_0,0] \sum_{j=0}^{\frac{n}{2}-1} [i',j'] a_j + [i_0,1] \sum_{j=\frac{i}{2}}^{n-1} [i',j'] a_j\\ & = [i_0,0] fwt(A_0)_{i'} + [i_0,1] fwt(A_1)_{i'} \end{aligned} \]

注意,\(A_0,A_1\) 的长度均为 \(\frac n 2\)。下标从 \(0\) 开始。

整理可得:

\[\begin{cases} fwt(A)_i = [0,0] fwt(A_0)_{i} + [0,1] fwt(A_1)_{i}, & (i \in [0,\frac{n}{2}) )\\ fwt(A)_{i+\frac{n}{2}} = [1,0] fwt(A_0)_{i} + [1,1] fwt(A_1)_{i}, & (i+\frac{n}{2} \in [\frac{n}{2},n)) \end{cases} \]

这样我们就通过 \(O(n)\) 的合并复杂度把求 \(fwt(A)_i,i\in[0,n)\) 变成求 \(fwt(A_{0/1})_i,i\in [0,\frac{n}{2})\) 了。

这种合并一共要做 \(\log n\) 层,因此总时间复杂度是 \(O(n \log n)\)

求 FWT 逆运算

由点值 \(fwt(A)\) 求插值 \(A\)

\(2 \times 2\) 的矩阵求逆,相当于贡献系数求逆,其他步骤和上面一样。

注意我们构造的矩阵要有逆。

因此上面说的 \([i,j]\) 的取值不一定是 \([0,1]\)


下面用线性代数证明为什么是对的。但是我并不会线代,所以只能感性理解一下了

\[fwt(A)_i=\sum_{j=0}^{n-1} [i,j] a_j \]

写作矩阵形式,会更容易理解:

\[\sum_{j=0}^{n-1} [i,j] a_j = fwt(A)_i\\ \begin{bmatrix} [0,0] & \cdots & [0,n-1] \\ \vdots & & \vdots\\ [n-1,0] & \cdots & [n-1,n-1] \end{bmatrix} \times \begin{bmatrix} a_0 \\ \vdots\\ a_{n-1} \end{bmatrix} = \begin{bmatrix} fwt(A)_0 \\ \vdots\\ fwt(A)_{n-1} \end{bmatrix} \]

记前面那个矩阵为 \(M\)。逆 FWT 就是用 \(M^{-1}\) 乘上 \(fwt(A)\) 序列。


对于异或卷积,\(M = M^T\)

异或卷据的变换矩阵具有准正交性,即满足 \(M^T \cdot M = nI\) 的矩阵。(\(n\) 是序列长度,需为 \(2\) 的幂)

至于为什么有这个性质我不知道。

所以 \(M^{-1} = \frac 1 n M^T\)


对于与、或、异或卷积,也可以直接求 \(M^T\)

因为 FWT 的性质,所以对于这些卷积,将子矩阵

\[\begin{bmatrix} [0,0]&[0,1]\\ [1,0]&[1,1] \end{bmatrix} \]

取逆,刚好就可以。

至于为什么刚好我不知道。

其中异或卷积的变换矩阵的子矩阵取逆后每个数刚好是原矩阵的 \(\frac12\)

code

不能理解,不是很好背诵。又学不会,又背不会,真是文理兼废,怎么办呢?

大概理解了,希望过两天还可以看懂。

当然,这是之前学的时候发的牢骚了

函数:

void calc() { rep(i,0,n-1) _mul(a[i],b[i]); }
void OR(int *f,int x=1) {
    for(int o=2,k=1; o<=n; o<<=1,k<<=1)     
        for(int i=0; i<n; i+=o)
            rep(j,0,k-1) _add(f[i+j+k],mul(f[i+j],x));
}
void AND(int *f,int x=1) {
    for(int o=2,k=1; o<=n; o<<=1,k<<=1)     
        for(int i=0; i<n; i+=o)
            rep(j,0,k-1) _add(f[i+j],mul(f[i+j+k],x));
}
void XOR(int *f,int x=1) {
    for(int o=2,k=1; o<=n; o<<=1,k<<=1)     
        for(int i=0; i<n; i+=o)
            rep(j,0,k-1) 
                _add(f[i+j],f[i+j+k]), f[i+j+k]=add(f[i+j],mod-add(f[i+j+k],f[i+j+k])), _mul(f[i+j],x), _mul(f[i+j+k],x);
}

调用方式:

OR(a), OR(b), calc(), OR(a,mod-1);
AND(a), AND(b), calc(), AND(a,mod-1);
XOR(a), XOR(b), calc(), XOR(a,ksm(2));

拓展

FWT 的每一位可以任意顺序,还可以每一位使用不同的位运算。

但是我好像没有完全理解为什么。

posted @ 2025-01-03 20:36  wing_heart  阅读(70)  评论(0)    收藏  举报