>
快速沃尔什变换(FWT)是解决这样一类卷积问题:
其中, 是位运算的一种。举个例子,给定数列 ,求:
看到 FWT 的名字,我们可以联想到之前学过的 FFT(很可惜,我没有写过 FFT 的笔记,所以没有链接),先看看 FFT 的原理:
综上,时间复杂度是 的。
在 FFT 中,我们构造了 为 的点值表示法,这么做满足 且容易变换。
其实 FWT 的思想也是一样的,主要也是需要构造 ,使得其满足 且可以快速变换。下面我们举 (按位或)、(按位与)和 (按位异或)为例。
因为数列长度是 的幂会更好处理,所以下文认为数列长度为 。
我们可以构造 。看看为什么需要这么构造。
首先,它满足 :
其次,它可以快速变换。举顺变换的例子。类比 FFT 的步骤,我们采用分治的方法来处理它。假设目前考虑到第 位,其中 和 是 位分治的结果:
其中, 是数列 的左半部分, 是 的右半部分。 函数就是把两个数列像拼接字符串一样拼接起来。 则是将两个数列对应相加。
这么做为什么是正确的呢?容易发现, 恰好是当前处理到的二进制位为 的子数列, 则是当前处理到的二进制位为 的子数列。若当前位为 ,则只能取二进制位为 的子数列 才能使得 。而若当前位为 ,则两种序列都能取。
考虑逆变换,则是将加上的 减回去:
下面我们给出代码实现。容易发现顺变换和逆变换可以合并为一个函数,顺变换时 ,逆变换时 。
void OR(int a[], int n, int tp) {
for (int len = 2; len <= n; len <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = i; j < i + (len >> 1); j ++ ) {
add(a[j + (len >> 1)], a[j] * tp);
}
}
}
}
同理构造 。 的正确性不证了。
容易发现, 恰好是当前处理到的二进制位为 的子数列, 则是当前处理到的二进制位为 的子数列。若当前位为 ,则只能取二进制位为 的子数列 才能使得 。而若当前位为 ,则两种序列都能取。
下面我们给出代码实现。顺变换时 ,逆变换时 。
void AND(int a[], int n, int tp) {
for (int len = 2; len <= n; len <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = i; j < i + (len >> 1); j ++ ) {
add(a[j], a[j + (len >> 1)] * tp);
}
}
}
}
发现异或有点难搞,对此我们引入一个新的运算符 。定义 ,其中 表示二进制下 的个数,并重申一下 表示按位与。
不用慌,我们也不需要你真正实现一个 ,它仅仅只是作为一个理解的辅助罢了。
我们发现它满足 。(重申一下 表示按位异或)
感性证明:发现这个新的运算符 其实就是 与 相同位数的奇偶性。若 ,则 与 、 与 相同位数个数奇偶性相同,所以 和 相同位数个数奇偶性也是相同的 ;若 ,则 与 、 与 相同位数个数奇偶性不同,所以 和 相同位数个数奇偶性也是不同的。
设 。我们来证一下 的正确性:
来看看怎么快速计算 的值,依旧是分治:
对于 在当前位为 的子数列 ,进行 运算时发现它和 计算或和 计算结果都不会变(因为 ),所以 中的 。
对于 在当前位为 的子数列 ,进行 运算时发现它和 计算结果是 ,和 计算结果是 (因为 )。
综上,有:
也就是:
逆变换易得:
给出代码,顺变换时 ,逆变换时 。
void XOR(int a[], int n, int tp) {
for (int len = 2; len <= n; len <<= 1) {
int k = (len >> 1);
for (int i = 0; i < n; i += len) {
for (int j = i; j < i + k; j ++ ) {
int ls = a[j], rs = a[j + k];
a[j] = 1ll * (ls + rs) % mod * tp % mod;
a[j + k] = 1ll * (ls - rs + mod) % mod * tp % mod;
}
}
}
}
类似 DFT,我们构造出的 FWT 也是一个线性变换。所以我们能得出如下性质:
「洛谷 P4717」【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)
求 、、 的三种卷积。。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
#define fir first
#define sec second
#define ep emplace
#define eb emplace_back
#define lowbit(x) ((x) & (-(x)))
#define add(a, b) ((a) = ((a) + (b) >= mod))
inline int read() {
int x = 0, f = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return x * f;
}
const int N = 131075;
const int mod = 998244353;
int lgn, n, a[N], b[N], c0[N], c1[N];
#define add(a, b) { \
if (((a) = (a) + (b)) >= mod) (a) -= mod; \
if (a < 0) a += mod; \
}
void OR(int a[], int n, int tp) {
for (int len = 2; len <= n; len <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = i; j < i + (len >> 1); j ++ ) {
add(a[j + (len >> 1)], a[j] * tp);
}
}
}
}
void AND(int a[], int n, int tp) {
for (int len = 2; len <= n; len <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = i; j < i + (len >> 1); j ++ ) {
add(a[j], a[j + (len >> 1)] * tp);
}
}
}
}
void XOR(int a[], int n, int tp) {
for (int len = 2; len <= n; len <<= 1) {
int k = (len >> 1);
for (int i = 0; i < n; i += len) {
for (int j = i; j < i + k; j ++ ) {
int ls = a[j], rs = a[j + k];
a[j] = 1ll * (ls + rs) % mod * tp % mod;
a[j + k] = 1ll * (ls - rs + mod) % mod * tp % mod;
}
}
}
}
int main() {
lgn = read(), n = 1 << lgn;
for (int i = 0; i < n; i ++ ) a[i] = c0[i] = read();
for (int i = 0; i < n; i ++ ) b[i] = c1[i] = read();
OR(c0, n, 1), OR(c1, n, 1);
for (int i = 0; i < n; i ++ ) c0[i] = 1ll * c0[i] * c1[i] % mod;
OR(c0, n, -1);
for (int i = 0; i < n; i ++ ) {
printf("%d ", c0[i]);
c0[i] = a[i], c1[i] = b[i];
}
puts(""), AND(c0, n, 1), AND(c1, n, 1);
for (int i = 0; i < n; i ++ ) c0[i] = 1ll * c0[i] * c1[i] % mod;
AND(c0, n, -1);
for (int i = 0; i < n; i ++ ) {
printf("%d ", c0[i]);
c0[i] = a[i], c1[i] = b[i];
}
puts(""), XOR(c0, n, 1), XOR(c1, n, 1);
for (int i = 0; i < n; i ++ ) c0[i] = 1ll * c0[i] * c1[i] % mod;
XOR(c0, n, 499122177);
for (int i = 0; i < n; i ++ ) printf("%d ", c0[i]);
return 0;
}
给定两个长度为 ()的序列 和 ,你需要求出一个序列 ,其中 满足:
对于 这一限制可以直接用 FWT/FMT 解决。
容易发现 等价于 ,我们引入 占位多项式:
求 时,可以枚举拼成 的两个集合大小 和 ,我们有:
也就是:
其中 为或卷积,由于 FWT 是线性变换,因此一个 在 后的结果 仍然是满足 FWT 性质的。
因此我们再做 IFWT 还原即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
#define fir first
#define sec second
#define ep emplace
#define eb emplace_back
#define lowbit(x) ((x) & (-(x)))
#define add(a, b) { \
if (((a) = (a) + (b)) >= mod) (a) -= mod; \
if ((a) < 0) (a) += mod; \
}
inline int read() {
int x = 0, f = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return x * f;
}
const int N = 1048580;
const int mod = 1e9 + 9;
int n, m, a[21][N], b[21][N], h[21][N];
void OR(int a[], int n, int tp) {
for (int len = 2; len <= n; len <<= 1) {
for (int i = 0; i < n; i += len) {
for (int j = i; j < i + (len >> 1); j ++ ) {
add(a[j + (len >> 1)], a[j] * tp);
}
}
}
}
int main() {
n = read(), m = 1 << n;
for (int i = 0; i < m; i ++ ) a[__builtin_popcount(i)][i] = read();
for (int i = 0; i < m; i ++ ) b[__builtin_popcount(i)][i] = read();
for (int i = 0; i <= n; i ++ ) OR(a[i], m, 1), OR(b[i], m, 1);
for (int i = 0; i <= n; i ++ ) {
for (int j = 0; j <= i; j ++ ) {
for (int k = 0; k < m; k ++ ) {
add(h[i][k], 1ll * a[j][k] * b[i - j][k] % mod);
}
}
}
for (int i = 0; i <= n; i ++ ) OR(h[i], m, -1);
for (int i = 0; i < m; i ++ ) printf("%d ", h[__builtin_popcount(i)][i]);
return 0;
}
参考资料: