这篇博客是上篇博客的补充,重点在于如何正确构造变换的函数。
先考虑或卷积的较简单的情况:
考虑我们现在要构造函数 (F(x)),满足:
考虑到需要满足变换前后的次数不变,于是我们可以肯定 (F) 一定是一种线性变换,于是我们可以先将变换表示成:
那么:
这时,我们代入需要变换的函数(或函数):
化简:
比较系数,我们有:
在更普遍的情况下,这就是:
回归正题,这种情况下,一种显然成立的函数就是 (F(n,i)=[n|i=n]),这就是上一篇文章中的构造来源。
考虑完了或变换,我们来考虑与变换:
一种显然的构造就是 (F_{n,j}=[n&j=n]),也就是说,这是一个超集的和。
那么我们可以倒着做高维前缀和,也就是说,我们实际做的是一个高维后缀和:
void FMT(int *a, int N, int op) {
for(int i = 1; i < N; i <<= 1)
for(int j = 0; j < N; ++j)
if(j & i) a[j - i] += op * a[j];
}
这里并不需要倒着枚举每一个数,是因为每一维都只可能是 1 向 0 转移,也就是说我们假想的无向图当中没有长度大于 1 的链。
让我们考虑一种更复杂的情况:异或卷积。也就是说,我们要求:
什么函数满足异或等于乘法呢?答案是 (1) 的个数的奇偶性。也就是说,(F_{n,j}=(-1)^{|j|}) 是可行的,是对的吗?
按照上述的理论,(F_{n,j}=(-1)^{|j|}) 是完全没有问题的,但是问题在于这个函数不具有逆变换!也就是说,有很多不同的 (a_n) 可以对应到同一个 (hat a_n)。
考虑如何构造一个可行的函数,这就要求 (F_{n,j}) 同时与 (n,j) 有关,这才能保证它具有可逆性(事实上还需要线性无关,但一般都可以)。那么考虑到异或对与运算有特殊的分配率:
于是可以构造函数 (F_{n,j}=(-1)^{|n&j|}),也即:
这不能再像 FMT 一样做高维前缀和,而是需要使用特别的分治技巧,这就是快速沃尔什变换(FWT)。
我们考虑分治这个过程,以下均假设下标从 (0) 到 (n-1),(n) 是 (2) 的幂。那么假定我们已经得到了只考虑前半部分的结果 (hat a_0) 和只考虑后半部分的结果 (hat a_1)(这两者都是长为 (frac n2) 的数列,且不考虑当前的最高位),现在需要合并成 (hat a)。那么根据柿子,(hat a) 的前半部分显然二者均可以贡献 1,而后半部分前者的可以贡献 (1),后者可以贡献 (-1),这是因为后者的最高位现在变成了 (1),这是可以进行贡献的。也就是说:
那么其逆变换就是:
这个式子也是像之前的柿子一样,不需要特意反着做,这可以通过数学归纳法得到。
void FWT(int *a, int N, double op) {
for(int len = 1; len < n; len <<= 1)
for(int i = 0; i < n; i += 2 * len)
for(int k = i; k < i + len; ++k) {
int g = a[k], h = a[k + len];
a[k] = (g + h) * op, a[k + len] = (g - h) * op;
}
}
或卷积和与卷积也可以使用同样的方法得出,且常数是直接高维前缀和的一半(是因为每一次都是找到了恰好有贡献的位置,高位前缀和也可以稍微改一下减小常数)。
通过同样的方法也可以得到 FFT 的卷积系数。而借此也可以得到一些其他奇奇怪怪的卷积的系数。