[学习笔记]FWT(快速沃尔什变换)

写在前面

本蒟蒻很菜,所以这篇博客依然几乎不会有证明

还是先说这东西是干什么的

平时我们所见的卷积是这样的:

\[h(k) = \sum_{i + j = k} f(i) \cdot g(j) \]

但是偶尔还会遇到条件中的加号变成其它符号的情况,\(FWT\)就是这个符号是按位与,或,异或时快速求解\(h\)

主要的三种情况

其实就是我们要构造一种变换\(tf\),使得\(tf(h) = tf(f) \cdot tf(g)\),其中点乘表示对应位相乘

或卷积(|)

即:

\[h(k) = \sum_{i | j = k} f(i) \cdot g(j) \]

我们可以构造变换\(FWT(A)\),使得\(FWT(A)_i = \sum_{j | i = i} A_j\)(我也不知道这个方法是怎么想出来的……)

但是验证它还是不难的,首先有这个:\(i | j | k = k \Rightarrow i | k = k, j | k = k\)

那么

\[\begin{align} FWT(h)_i = \sum_{j | i = i} h_j & = \sum_{j | i = i} \sum_{s | t = j} f(s) \cdot g(t) \\ & = \sum_{s | i = i} \sum_{t | i = i} f(s) \cdot g(t) \\ & = \sum_{s | i = i} f(s) \sum_{t | i = i} g(t) = FWT(f)_i \cdot FWT(g)_i \end{align} \]

然后问题变成了快速求\(FWT(A)\)

由于这个变换满足下面的等式:

\[FWT(A) = (FWT(A_0), FWT(A_0) + FWT(A_1)) \]

其中\(A_0\)表示序列前半段,\(A1\)表示序列后半段,加号表示对应位置相加。简单理解一下就是根据定义,\(FWT(A)_i\)\(i\)的所有子集\(j\)\(A_j\)之和,而序列前半段的下标二进制最高位为\(0\),增加这一位并不会产生新的子集,后半段下标二进制最高位为\(1\),增加这一位后前面高位为\(0\)的部分也会成为新的子集

所以显然就可以每次将序列分成前后两段分别计算,然后合并即可

然后怎么逆变换呢,根据上面的正变换,不难发现就是减掉所有它的子集处的值就是了,可以得出:

\[IFWT(A) = (IFWT(A_0), IFWT(A_1) - IFWT(A_0)) \]

与卷积(&)

与和或在各个方面都联系紧密,那么就可以类比或卷积,不难得出:

\[\begin{align} FWT(A) & = (FWT(A_0) + FWT(A_1), FWT(A_1)) \\ IFWT(A) & = (IFWT(A_0) - IFWT(A_1), IFWT(A_1)) \end{align} \]

异或卷积(^)

这个比较复杂,蒟蒻我并没能吃透,好像和奇偶性有关??

结论是:

\[\begin{align} FWT(A) & = (FWT(A_0) + FWT(A_1), FWT(A_0) - FWT(A_1)) \\ IFWT(A) & = (\frac{IFWT(A_0) + IFWT(A_1)}{2}, \frac{IFWT(A_0) - IFWT(A_1)}{2}) \end{align} \]

另外一个很少见的运算——同或(\(\odot\))

直接上结论了:

\[\begin{align} FWT(A) & = (FWT(A_1) - FWT(A_0), FWT(A_1) + FWT(A_0)) \\ IFWT(A) & = (\frac{IFWT(A_1) - IFWT(A_0)}{2}, \frac{IFWT(A_1) + IFWT(A_0)}{2}) \end{align} \]

代码

模板题戳这里

因为没见到同或的题,也没有看见过同或的模板,下面的代码只有前三种运算QwQ

还有就是取模意义下除以\(2\)转换为乘上逆元QwQ

void inc(LL &x, LL y) { x += y; if (x >= mod) x -= mod; }
void dec(LL &x, LL y) { x -= y; if (x < 0) x += mod; }
/*
	type == 0  =>  or
	type == 1  =>  and
	type == 2  =>  xor
*/
void FWT(LL *arr, int n, int type) {
	for (int len = 2, half = 1; len <= (1 << n); len <<= 1, half <<= 1)
		for (int i = 0; i < (1 << n); i += len)
			for (int j = 0; j < half; ++j)
				if (type == 0) inc(arr[i + half + j], arr[i + j]);
				else if (type == 1) inc(arr[i + j], arr[i + half + j]);
				else {
					LL a0 = arr[i + j], a1 = arr[i + half + j];
					inc(arr[i + j], a1); dec(arr[i + half + j] = a0, a1);
				}
}
void IFWT(LL *arr, int n, int type) {
	for (int len = 2, half = 1; len <= (1 << n); len <<= 1, half <<= 1)
		for (int i = 0; i < (1 << n); i += len)
			for (int j = 0; j < half; ++j)
				if (type == 0) dec(arr[i + half + j], arr[i + j]);
				else if (type == 1) dec(arr[i + j], arr[i + half + j]);
				else {
					LL a0 = arr[i + j], a1 = arr[i + half + j];
					arr[i + j] = (a0 + a1) * inv % mod;
					arr[i + half + j] = (a0 - a1) * inv % mod;
					if (arr[i + half + j] < 0) arr[i + half + j] += mod;
				}
}
posted @ 2019-03-09 20:53  Rhein_E  阅读(468)  评论(0编辑  收藏  举报