Loading

多项式 - 快速沃尔什变换

概述

\(FWT\) 解决的问题和核心思想与 \(FFT\) 类似,都是处理形如

\[C_k=\sum_{i\odot j=k} a_i\times b_j \]

的卷积,其中 \(\odot\) 是一种二元运算。在 \(FFT\) 中一般是 \(+\) ,而 \(FWT\) 处理的一般是位运算 \(\and\) , \(\or\) , \(\oplus\)

核心思想上,\(FWT\) 希望通过一种可逆的 \(\boldsymbol{线性}\)变换 \(FWT[A]\) 变换 \(A\) 序列与 \(B\) 序列,
使得 \(FWT[C] = FWT[A] \cdot FWT[B]\)
再通过逆变换 \(IFWT[C]\) 得到 \(C\) 序列。其中 \(\cdot\) 是对应位相乘。

详述

贡献系数

我们用小写字母来表示原序列,大写字母来表示变换后的序列。
我们沿用 \(FFT\) 的理解,将 \(\boldsymbol{线性}\)变换理解为乘上系数矩阵。

\[ \begin{bmatrix} a_0 & a_1 &\dots&a_{n-1} \end{bmatrix} \times \begin{bmatrix} f(0, 0)&f(1, 0)&\dots&f(n-1, 0)\\f(0, 1)&f(1,1)&\dots&f(n-1, 1)\\ f(0,2)&f(1,2)&\cdots&f(n-1,2)\\ \vdots&\vdots&\ddots&\vdots\\ f(0,n-1)&f(1,n-1)&\cdots&f(n-1,n-1) \end{bmatrix}=\begin{bmatrix} A_0 & A_1 &\dots&A_{n-1} \end{bmatrix} \]

\(A_i=\sum_j f(i, j)a_j\)

现在,我们希望 \(f\) 满足两个性质。
\(\boldsymbol{这里注意,尽管我们叫它性质,但其实称其为我们需要满足的限制似乎更适合}\)

  1. \(f(i, j)\times f(i, k) = f(i, j\odot k)\)
  2. 可以拆位贡献。

性质一:

\[\begin{align*} A_i\times B_i &=\sum_j f(i, j)a_j \times \sum_k f(i, k)b_k\\ &=\sum_j\sum_k f(i, j) \times f(i, k) \times a_j b_k \end{align*} \]

同时,

\[\begin{align*} C_i&=\sum_x f(i, x)c_x\\ &=\sum_x f(i, x)\sum_{j\odot k=x} a_j\times b_k\\ &=\sum_j\sum_k f(i, j\odot k) \times a_j b_k \end{align*} \]

所以只要 \(f(i, j) \times f(i, k) = f(i, j\odot k)\) ,就可以保证 \(C_i = A_i \times B_i\)

性质二:
我们发现上一条性质没有用到位运算的性质,所以对于任意运算都是成立的。
而这条是 \(FWT\) 位运算所需的特殊性质。
等价于我们需要满足 \(f(i, j) = \prod_k f(i_k, j_k)\) ,其中 \(i_k\) 表示 \(i\) 二进制下的第 \(k\) 位。
如果我们拥有 \(f(0, 0)\), \(f(0, 1)\), \(f(1, 0)\), \(f(1, 1)\), 就可以递推出整个矩阵,所以这个性质也是好满足的。
且只要这个 \(2\times 2\) 的位矩阵可逆,整个矩阵就是可逆的。
注意,我们只是要求其满足这样的性质,并没有关于子集什么的实际含义。

Ps:和 \(FFT\) 对照着来看,\(FFT\) 矩阵的性质是由 \(w_n^k\) 保证的,而现在我们通过拆位来给矩阵赋予性质。

迭代过程

我们先假定我们已经拥有了一个可逆的贡献矩阵,来看看应该怎么转换。

\[\begin{align*} A_i&=\sum_{j=0}^{n-1} f(i,j)a_j \\ &\text{按照当前第 $k$ 位情况分开}\\ &=\sum_{j=0}^{\frac{n}{2}-1} f(i, j)a_j + \sum_{j=\frac{n}{2}}^{n-1} f(i, j)a_j \\ &\text{记 $i'$ 为 $i$ 的当前位附为 0 后的结果,$j'$ 同理}\\ &=f(i_k, 0) \times \sum_{j=0}^{\frac{n}{2}-1} f(i', j')a_j + f(i_k, 1) \times \sum_{j=\frac{n}{2}}^{n-1} f(i', j')a_j \end{align*} \]

然后我们就可以迭代或递归地处理这个变换了。
\(A_i'\) 为上一轮变换后的值,考虑如何进行下一次变换。
\(i'\)\(k\) 位是 \(0\)\(i^0\) , 第 \(k\) 位是 \(1\) 的为 \(i^1\)

\[A_{i^0} = f(0, 0) \times A_{i^0}' + f(0, 1) \times A_{i^1}' \\ A_{i^1} = f(1, 0) \times A_{i^0}' + f(1, 1) \times A_{i^1}' \]


void FWT(int *A) {
	for (int len = 2; len <= n; len <<= 1)
		for (int i = 0; i + len - 1 < n; i += len)
			for (int j = i; j < i + len / 2; ++j) { f0 = A[j], f1 = A[j + len / 2];
				A[j] = (1ll * f0 * f[0][0] % mod + 1ll * f1 * f[0][1] % mod) % mod;
				A[j + len / 2] = (1ll * f0 * f[1][0] % mod + 1ll * f1 * f[1][1] % mod) % mod;
			}
}

观察上面的迭代过程,因为每一位的贡献是独立的,并且没有实际含义,所以我们完全可以按照任意顺序枚举 \(len\)
同样的,还原过程也是不讲究顺序的。

求贡献矩阵

下面以 \(\or\) 操作为例。
根据上面的分析,我们只需要得到

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

根据性质 \(1\) ,省略第一维可以列出下列方程组:

\[\begin{cases} f(0)\times f(0) = f(0)\\ f(0)\times f(1) = f(1)\\ f(1)\times f(1) = f(1)\\ \end{cases} \]

取两组不同解,比如可以取

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

注意我们要求其有逆,所以不能取

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

合法矩阵的逆为

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

逆变换就是将上文中代码的 \(f\) 换成 \(f^{-1}\) 即可。

线性变换

按照上述理解,可以发现 \(FWT\) 是线性变换。

\[FWT[A + B] = FWT[A] + FWT[B] \\ FWT[c \times A] = c \times FWT[A] \]

posted @ 2025-07-08 22:45  qkhm  阅读(49)  评论(0)    收藏  举报