快速莫比乌斯变换(FMT)和快速沃尔什变换(FWT)

这篇博客是上篇博客的补充,重点在于如何正确构造变换的函数。


先考虑或卷积的较简单的情况:

\[a_n=\sum_{i|j=n}b_ic_j \]

考虑我们现在要构造函数 \(F(x)\),满足:

\[\hat a_n=F(a_n)=\hat b_n\cdot\hat c_n \]

考虑到需要满足变换前后的次数不变,于是我们可以肯定 \(F\) 一定是一种线性变换,于是我们可以先将变换表示成:

\[\hat a_n=\sum_{i=0}^NF_{n,i}a_i \]

那么:

\[\sum_{i=0}^NF_{n,i}a_i=\sum_{i=0}^NF_{n,i}b_i\sum_{i=0}^NF_{n,i}c_i \]

这时,我们代入需要变换的函数(或函数):

\[\sum_{i=0}^NF_{n,i}\sum_{j|k=i}b_jc_k=\sum_{i=0}^NF_{n,i}b_i\sum_{i=0}^NF_{n,i}c_i \]

化简:

\[\sum_{j=0}^N\sum_{k=0}^NF_{n,j|k}b_jc_k=\sum_{j=0}^N\sum_{k=0}^NF_{n,j}F_{n,k}b_jc_k \]

比较系数,我们有:

\[F_{n,j}F_{n,k}=F_{n,j|k} \]

在更普遍的情况下,这就是:

\[a_n=\sum_{f(j,k)=n}b_jc_k\Leftrightarrow F_{n,j}F_{n,k}=F_{n,f(j,k)} \]

回归正题,这种情况下,一种显然成立的函数就是 \(F(n,i)=[n|i=n]\),这就是上一篇文章中的构造来源。


考虑完了或变换,我们来考虑与变换:

\[F_{n,j}F_{n,k}=F_{n,j\&k} \]

一种显然的构造就是 \(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 的链。


让我们考虑一种更复杂的情况:异或卷积。也就是说,我们要求:

\[F_{n,j}F_{n,k}=F_{n,j\oplus k} \]

什么函数满足异或等于乘法呢?答案是 \(1\) 的个数的奇偶性。也就是说,\(F_{n,j}=(-1)^{|j|}\) 是可行的,是对的吗?

按照上述的理论,\(F_{n,j}=(-1)^{|j|}\) 是完全没有问题的,但是问题在于这个函数不具有逆变换!也就是说,有很多不同的 \(a_n\) 可以对应到同一个 \(\hat a_n\)

考虑如何构造一个可行的函数,这就要求 \(F_{n,j}\) 同时与 \(n,j\) 有关,这才能保证它具有可逆性(事实上还需要线性无关,但一般都可以)。那么考虑到异或对与运算有特殊的分配率:

\[(i\&j)\otimes(i\&k)=i\&(j\otimes k) \]

于是可以构造函数 \(F_{n,j}=(-1)^{|n\&j|}\),也即:

\[\hat a_n=\sum_{i=0}^N(-1)^{|n\&i|}a_i \]

这不能再像 FMT 一样做高维前缀和,而是需要使用特别的分治技巧,这就是快速沃尔什变换(FWT)。

我们考虑分治这个过程,以下均假设下标从 \(0\)\(n-1\)\(n\)\(2\) 的幂。那么假定我们已经得到了只考虑前半部分的结果 \(\hat a_0\) 和只考虑后半部分的结果 \(\hat a_1\)(这两者都是长为 \(\frac n2\) 的数列,且不考虑当前的最高位),现在需要合并成 \(\hat a\)。那么根据柿子,\(\hat a\) 的前半部分显然二者均可以贡献 1,而后半部分前者的可以贡献 \(1\),后者可以贡献 \(-1\),这是因为后者的最高位现在变成了 \(1\),这是可以进行贡献的。也就是说:

\[\hat a=(\hat a_0+\hat a_1,\hat a_0-\hat a_1) \]

那么其逆变换就是:

\[a=(\frac{a_0+a_1}{2},\frac{a_0-a_1}{2}) \]

这个式子也是像之前的柿子一样,不需要特意反着做,这可以通过数学归纳法得到。

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 的卷积系数。而借此也可以得到一些其他奇奇怪怪的卷积的系数。

posted @ 2020-11-15 23:40  whx1003  阅读(475)  评论(0编辑  收藏  举报