Fast Walsh–Hadamard Transform
Introduction
The Walsh Transform1 is a method used in spectral analysis as an alternative to the Discrete Fourier Transform, widely applied in signal processing. FFT uses double precision, but Walsh decomposes the signal under square waves of different oscillation frequencies, so all coefficients are integers with the same absolute magnitude. This eliminates the need for floating-point multiplication, improving computational speed.
Thus, the core idea of FWT and FFT should be the same: both are transformations of arrays. We denote the result of applying the Fast Walsh Transform to array \(A\) as \(FWT[A]\).
The core idea of FWT is:
We need a new sequence \(C\), obtained from sequences \(A\) and \(B\) through some operation rule, i.e., \(C = A \cdot B\);
We first compute \(FWT[A]\) and \(FWT[B]\) in the forward direction, then compute \(FWT[C]=FWT[A] \cdot FWT[B]\) in \(O(n)\) time complexity, where \(\cdot\) denotes element-wise multiplication of sequences;
Then we perform the inverse operation to obtain the original sequence \(C\). The time complexity is \(O(n \log{n})\).
In competitive programming, FWT is used to solve convolution problems where indices undergo bitwise operations.
Formula: \(C_{i} = \sum_{i=j \oplus k}A_{j} B_{k}\)
(where \(\oplus\) is one of the binary bitwise operations)
Below we take \(\cup\) (bitwise OR), \(\cap\) (bitwise AND), and \(\oplus\) (bitwise XOR) as examples.
Operations of FWT
OR Operation
If \(k=i\cup j\), then the positions where \(i\)'s binary bits are \(1\) and where \(j\)'s binary bits are \(1\) must be subsets of the positions where \(k\)'s binary bits are \(1\).
Now to obtain \(FWT[C] = FWT[A] \cdot FWT[B]\), we need to construct the FWT rule.
By definition, we can construct \(FWT[A]_i = A'_i = \sum_{i=i\cup j}A_{j}\), meaning \(j\) satisfies that its binary \(1\) bits are a subset of \(i\)'s.
Then we have:
Next, let's see how to compute \(FWT[A]\).
Obviously, we cannot enumerate all, as the complexity would be \(O(n^2)\). Since we cannot enumerate entirely, we consider divide and conquer.
We split the entire interval in half. After splitting, writing the indices in binary reveals a pattern.
Let \(A_0\) denote the first half of \(A\), and \(A_1\) denote the second half. Then \(A_0\) corresponds to indices where the highest bit is \(0\), and its subsets are exactly its own subsets (since the highest bit is \(0\)). However, \(A_1\) has highest bit \(1\), and its qualifying subsets include not only itself but also subsets with highest bit \(0\), i.e.,
Here merge means concatenating two arrays like strings, and \(+\) is ordinary addition, meaning adding corresponding binary bits.
Thus, we can complete the concatenation in \(O(\log{n})\) time via binary splitting, with each concatenation requiring one operation, meaning we obtain \(FWT[A]\) in \(O(n\log{n})\) time complexity.
Next is inversion. Actually, inversion is simple. Since we know \(A_0\)'s subsets are itself (\(A_0 = FWT[A_0]\)), and \(A_1\)'s subsets are \(FWT[A_0] + FWT[A_1]\), we easily derive the inversion recurrence:
Below we provide code implementation. Note that forward and inverse transforms can be merged into one function, with \(\text{type}=1\) for forward transform and \(\text{type}=-1\) for inverse transform.
Implementation
1 2 3 4 5 6 7 8 9 10 | |
AND Operation
Analogous to OR, we can derive similar conclusions:
Below we provide code implementation. \(\text{type}=1\) for forward transform, \(\text{type}=-1\) for inverse transform.
Implementation
1 2 3 4 5 6 7 8 9 10 | |
XOR Operation
The convolution for XOR is based on the following principle:
If we let \(x\circ y\) denote the parity of the number of \(1\)s in \(x\cap y\), i.e., \(x\circ y=\text{popcnt}(x\cap y)\bmod 2\), then we easily have \((x\circ y)\oplus (x\circ z)=x\circ(y\oplus z)\).
The operation for \(FWT[A]\) is also easy to derive.
Let \(FWT[A]_i=\sum_{i\circ j=0}A_j-\sum_{i\circ j=1}A_j\). We verify the correctness of \(FWT[C] = FWT[A] \cdot FWT[B]\):
Now let's see how to compute \(A,B\) quickly, still using divide and conquer:
For the subsequence \(FWT[A_0]\) where \(i\)'s current bit is \(0\), when performing \(\circ\) operation, we find that computing with \(0\) or \(1\) does not change the result (since \(0\cap 0=0,0\cap1=0\)), so \(\sum_{i\circ j=1}A_j=0\) in \(FWT[A]=\sum_{i\circ j=0}A_j-\sum_{i\circ j=1}A_j\).
For the subsequence \(A_1\) where \(i\)'s current bit is \(1\), when performing \(\circ\) operation, computing with \(0\) yields \(0\), and with \(1\) yields \(1\) (since \(1\cap 0=0,1\cap1=1\)).
In summary:
That is:
Inverse transform is easily obtained:
Code is provided. \(\text{type}=1\) for forward transform, \(\text{type}=\frac{1}{2}\) for inverse transform.
Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
XNOR Operation
Analogous to XOR, we give the formulas:
\(FWT[A]_{i} = \sum_{C_1}A_{j} - \sum_{C_2}A_{j}\) (where \(C_1\) means \(\text{popcnt}(x\cup y)\bmod 2\) is \(0\), \(C_2\) means \(\text{popcnt}(x\cup y)\bmod 2\) is \(1\))
Another Perspective on FWT
Let \(c(i,j)\) be the contribution coefficient of \(A_j\) to \(FWT[A]_i\). We can redescribe the FWT transformation process:
Since:
We can easily prove: \(c(i,j)c(i,k)=c(i,j\odot k)\). Here \(\odot\) is any bitwise operation.
Additionally, the \(c\) function has an important property: it can be processed bitwise.
For example, during transformation:
This is inefficient. We split it:
Considering the difference between the first and second parts for \(i,j\), we find only the highest bit differs.
So we remove the highest bit of \(i,j\) to get \(i',j'\), and denote the highest bit of \(i\) as \(i_0\). Then:
If \(i_0=0\), then:
If \(i_0=1\), then:
That is, we only need:
These four numbers to complete the transformation. We call this matrix the bit matrix.
If we want the inverse transform, we need the inverse of the above bit matrix.
If the inverse matrix is \(c^{-1}\), we can recover the original numbers via a similar operation:
The inverse matrix may not exist; for example, if there is a row or column of \(0\)s, the matrix is not invertible. We must be careful when constructing.
Bitwise OR
We can construct:
This satisfies \(c(i,j)c(i,k)=c(i,j\cup k)\). We find this matches exactly what we derived earlier: \(FWT[A]=\text{merge}(FWT[A_0], FWT[A_0]+FWT[A_1])\)! Similarly, the following matrix also satisfies \(c(i,j)c(i,k)=c(i,j\cup k)\), but we generally use the one above:
Although the following matrix also satisfies \(c(i,j)c(i,k)=c(i,j\cup k)\), it has a row of \(0\)s, so it is not invertible and thus invalid:
For inverse transformation, we need to invert the matrix. Taking the topmost matrix as an example, we get:
Then, following the forward transform method, substitute the inverse transform matrix.
Bitwise AND
We can construct:
This satisfies \(c(i,j)c(i,k)=c(i,j\cap k)\).
Inverse matrix:
Bitwise XOR
We can construct:
This satisfies \(c(i,j)c(i,k)=c(i,j\oplus k)\).
Inverse matrix:
FWT is a Linear Transformation
FWT is a linear transformation. That is, it satisfies:
and:
K-dimensional FWT
Actually, bitwise operations essentially operate on an \(n\)-dimensional \(\{0,1\}\) vector. OR is taking \(\max\) in each dimension. AND is taking \(\min\) in each dimension. XOR is adding corresponding dimensions modulo \(2\).
Bitwise operations have a characteristic: each bit of the vector is independent.
We extend \(\{0,1\}\) to \([0,K)\cap \mathbf{Z}\), i.e., to base \(K\), and see what we get?
max Operation
We extend \(\cup\) to base \(K\), defining \(i\cup j\) as taking \(\max\) bitwise, with:
If \(j=k\), then the above becomes:
That is, the \(1\)s in each row must appear before the \(0\)s; if they appear after, it's invalid. Playing around yields a valid construction:
Inverting gives:
min Operation
We extend \(\cap\) to base \(K\), defining \(i\cap j\) as taking \(\min\) bitwise, with:
If \(j=k\), then the above becomes:
That is, the \(1\)s in each row must appear after the \(0\)s; if they appear before, it's invalid. Playing around yields a valid construction:
Inverting gives:
The first two are less commonly used; the following is more common:
Non-carry Addition
We extend \(\oplus\) to base \(K\), defining \(i\oplus j\) as adding bitwise then \(\bmod K\), with:
We construct \(c(i,j)=\omega_{K}^j\), which satisfies the requirement:
But if every row is the same, the matrix is not invertible. So we can construct \(c(i,j)=\omega_{K}^{(i-1)j}\).
We have the following matrix:
This is the Vandermonde matrix. Its inverse is:
If the modulus given in the problem has primitive roots of unity, we can implement this simply.
But primitive roots of unity may not exist modulo the given modulus, so we consider field extension: artificially define an \(x\) satisfying \(x^K=1\), then directly substitute \(x\) into computations. Each number becomes a polynomial of degree \(k-1\) in \(x\). We only need to compute modulo \(x^K-1\). Then the matrix can be expressed as:
But this may introduce zero divisors, meaning a number may have multiple representations, and we cannot determine a number's true value.
We consider not modulo \(x^K-1\), but modulo the cyclotomic polynomial \(\Phi_{K}(x)\), which satisfies that \(x\) has order \(k\) and is irreducible over \(\mathbb{Q}\). So we define the above computation to be modulo \(\Phi_{K}(x)\).
Another issue is that modulo \(\Phi_{K}(x)\) has large constants (since \(\Phi\) itself is a polynomial). But because \(\Phi_{K}(x)\mid x^k-1\), we can compute modulo \(x^k -1\) and finally reduce modulo \(\Phi_{K}(x)\).
Example Problems
「CF 1103E」Radix sum
Given a sequence \(a_1,a_2,...,a_n\) of length \(n\), for each \(p \in [0,n-1]\), find the number of integer sequences \(i_1,i_2,...,i_n\) satisfying the following conditions, modulo \(2^{58}\):
- \(\forall j \in [1,n] , i_j \in [1,n]\);
- \(\sum\limits_{j=1}^n a_{i_j} = p\), where addition is defined as decimal non-carry addition.
\(n\le10^5,a_i\le10^5\)
Solution
We can think of dp: design state \(f_{i,s}\) meaning considering the \(i\)-th number, current addition state is \(s\). Since FWT transformation is linear, we can first transform to FWT point-value representation, then raise to the \(n\)-th power, and finally transform back.
The above is straightforward, but the problem gives modulus \(2^{58}\). We find no primitive roots of unity, so consider field extension.
Here the cyclotomic polynomial is \(\Phi_{10}(x)=x^4-x^3+x^2-x+1\).
However, during UFWT, we need to divide by base \(10\), but we find \(10\) has no inverse modulo \(2^{58}\). Actually, we find \(5\) does have an inverse modulo \(2^{58}\): \(57646075230342349\). We just need to divide by \(2\) additionally. Let the answer after dividing by \(5\) be \(x\), the true answer be \(y\), i.e., \(2^5y\equiv x\pmod{2^{64}}\). Clearly, we have \(y\equiv \frac{x}{2^5}\pmod{2^{64-5}}\), i.e., \(y\equiv \frac{x}{2^5}\pmod{2^{59}}\), so we can directly divide the final answer by \(2^5\). Although the problem setter didn't know why modulo \(2^{58}\) was chosen, we can take modulo again.
【CF103329F】【XXII Opencup, Grand Prix of XiAn】The Struggle
Given an ellipse \(E\), where all integer point coordinates are within \([1,4 \cdot 10^6]\). Find \(\sum_{(x,y) \in E} (x \oplus y)^{33}x^{-2}y^{-1} \mod 10^9+7\).
Solution
This is a less straightforward problem. The problem setter provides detailed English solution; see this link.
References
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用