从线性变换的角度看待 FWT
upd on 2121/8/22: 修改了一些符号/语言错误。
Abstract
今天早上听本校高二的同学讲课,深受启发,下去自学了 FWT,决定以不同于大部分博客所提供的视角来介绍这个算法。
About FWT
FWT,即 Fast Walsh–Hadamard Transform,快速沃尔什变换。在 OI 中一般被用来处理位运算卷积问题。
Analysis
设
现在定义两个序列的位运算卷积
其中
我们希望构造这样的可逆线性变换
这样,我们就得到一条求位运算卷积的思路,对
我们现在来分析一下这个线性变换
我们推出了这样一条性质,下面我们将揭秘在各种卷积问题中这个线性变换
FFT
注意到
| 运算卷积
容易构造出
我们发现这实际上就是高维前缀和,其逆变换就是高维前缀差分。
代码如下
void FWT_or(int *a)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < (1 << n); j++)
{
if(j >> i & 1)
a[j] = (a[j] + a[j - (1 << i)]) % mod;
}
}
}
void IFWT_or(int *a)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < (1 << n); j++)
{
if(j >> i & 1)
a[j] = (a[j] - a[j - (1 << i)] + mod) % mod;
}
}
}
& 运算卷积
容易构造出
我们发现这实际上就是高维后缀和,其逆变换就是高维后缀差分。
代码如下
void FWT_and(int *a)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < (1 << n); j++)
{
if(!(j >> i & 1))
a[j] = (a[j] + a[j + (1 << i)]) % mod;
}
}
}
void IFWT_and(int *a)
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < (1 << n); j++)
{
if(!(j >> i & 1))
a[j] = (a[j] - a[j + (1 << i)] + mod) % mod;
}
}
}
xor 运算卷积
xor 与前面提到的 & 和 | 略有不同,所以我们需要寻找另外的方式。
注意到我们有等式
而将其转化为乘法的关系仅需联想到
由于一些原因我们希望
考虑分治,设
令
从而
其中
为了得到它的逆变换,我们对矩阵
所以我们仅需做一次正变换,再将每个元素除以
看到这里有没有觉得 FWT 和 FFT 很像?事实上,FWT 就是高维的循环卷积。
这部分的代码如下
void FWT_xor(int *a)
{
for(int mid = 1; mid < (1 << n); mid <<= 1)
{
for(int i = 0; i < (1 << n); i += (mid << 1))
{
for(int j = 0; j < mid; j++)
{
int x = a[i + j], y = a[i + j + mid];
a[i + j] = (x + y) % mod;
a[i + j + mid] = (x - y + mod) % mod;
}
}
}
}
void IFWT_xor(int *a)
{
for(int mid = 1; mid < (1 << n); mid <<= 1)
{
for(int i = 0; i < (1 << n); i += (mid << 1))
{
for(int j = 0; j < mid; j++)
{
int x = a[i + j], y = a[i + j + mid];
a[i + j] = 1ll * (x + y) * 1ll * inv2 % mod;
a[i + j + mid] = 1ll * (x - y + mod) * 1ll * inv2 % mod;
}
}
}
}