位运算卷积与 FWT
几行代码里隐藏数学真是高深莫测,只有勇者才能够发现这绝妙的规律。
位运算卷积
普通的卷积 (即多项式乘法) 是这个样子:
而位运算卷积就是将加号变为了二元位运算,就是这样:
其中
注意到,在多项式乘法中,如果两个多项式的界为
在下文中,向量
为了方便,矩阵
特殊情况
为了探寻规律,我们以异或为例,手动计算一下
$n = 2^0$
当输入两个数时,结果会是一个数。由于
So easy, 也没什么意思。
$n = 2^1$
输入两个向量
不如大胆的尝试一下,能否通过某种变换,从而能够使用更简单的点积来计算呢,就像下面这个样子:
这里
那么意味着我们要找到一个矩阵
首先这个线性变换只在二维向量空间上进行的,所以
那么之前是一个两个向量的点积式,于是我们可以列出两个方程来表示:
于是你发现第一个方程和第二方程没有区别......好吧,那就只研究第一个方程,将其暴力拆开是这样的:
一个二元二次方程,有很多解了啦,但是我们只需要一个。最简单的解法2就是对应的项的系数相同。
也就是说:
然后就是:
然后发现
不过我们不能够全部填
对于其逆矩阵,我在这里帮你们算出来了:
实在记不住就爆枚一下矩阵吧,这样的
$n = 2^m \;\; (m \geqslant 2)$
现在来考虑更复杂的情况。
跟前面一样的思想,我们企图找到
我们已经知道
基于这样一个事实:
这里
这有什么好处呢?我们先考虑最高位,这样将输入向量分为两部分:
下标为
对于结果
不难发现,这实际上退化为了
对于逆变换也是一样。
于是可以得知:
但实际上已经没有意义啦!因为
于是我们获得了 FWT 算法3!
FWT 算法
前面 BB 了一大段,现在来梳理一下:
- 我们计算
$TA$ 和$TB$ 。 - 然后答案就是
$T^{-1}(TA \cdot TB)$ 。
如何计算
以异或运算为例,假设我们钦定了使用这个矩阵作为我们的变换:
那么合并的过程就是这样4:
你只用按照矩阵的形式来计算就可以了。
对于逆变换,就是这样合并:
具体实现
为了方便理解,在这里给出用 Python 编写的 FWT 算法,依然是异或的例子。
递归形式
递归形式简单粗暴,容易编写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | import numpy def fwt(X): """正变换,返回TX X: 输入向量 """ if len(X) == 1: return X m = len(X) // 2 A0 = fwt(X[:m]) A1 = fwt(X[m:]) return numpy.array(*(A0 + A1), *(A0 - A1)) def rfwt(X): """逆变换,返回T^{-1}X X: 输入向量 """ if len(X) == 1: return X m = len(X) // 2 A0 = rfwt(X[:m]) A1 = rfwt(X[m:]) return numpy.array(*((A0 + A1) / 2), *((A0 - A1) / 2)) def product(A, B): """计算卷积 A, B: 输入向量 """ TA = fwt(A) TB = fwt(B) TC = TA * TB return rfwt(TC) # 调用 print(product( numpy.array([1, 2, 3, 4]), numpy.array([2, 3, 3, 3]) )) # [29, 28, 27, 26] |
迭代形式
与快速傅里叶变换类似,FWT 也可以改写为迭代的版本,具体的原理可以参见快速傅里叶变换的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import copy import numpy def iterative_fwt(X): A = copy.deepcopy(X) s = 2 while s <= len(X): for i in range(0, len(X), s): for j in range(0, s // 2): tmp = A[i + j] A[i + j] += A[i + j + s // 2] A[i + j + s // 2] = tmp - A[i + j + s // 2] s *= 2 return A def iterative_rfwt(X): A = copy.deepcopy(X) s = 2 while s <= len(X): for i in range(0, len(X), s): for j in range(0, s // 2): tmp = A[i + j] A[i + j] = (A[i + j] + A[i + j + s // 2]) / 2 A[i + j + s // 2] = (tmp - A[i + j + s // 2]) / 2 s *= 2 return A |
当然这里给出版本不是常数上效率最高的,大家可以根据实际情况改写代码。
其他的位运算
之前一直在讨论异或,没有关注与运算和或运算。因为它们的推导过程是一样的。这里就不重复其过程了。
对于与运算而言:
对于或运算而言:
实现它们的位运算卷积就只用修改合并的过程即可。
其他运用
子集和
现在是时候来看一下或卷积了,按照之前方法,求出或卷积变换的一种形式。这里我们关注的是
这就是子集和变换。同时利用
即逆子集和变换。计算它们的时间复杂度都是
子集卷积
利用或卷积,我们可以干些更有趣的事情,那就是子集卷积:对于两个向量
记
而或卷积可以等价地写成这样的形式:
与子集卷积相比,只是少了一个交集为空的条件。为了能够解决这个条件,我们可以从集合的大小下手。主要原因是大小只有
具体的做法如下,首先令:
类似的方法定义
下面是我的 Python 2 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | n = int(raw_input()) assert n & (n - 1) == 0, 'Argument "n" must be a power of 2.' f = map(int, raw_input().split()) g = map(int, raw_input().split()) def fwt(X): if len(X) == 1: return X m = len(X) >> 1 A, B = fwt(X[:m]), fwt(X[m:]) for i in xrange(m): A.append(A[i] + B[i]) return A def rfwt(X): if len(X) == 1: return X m = len(X) >> 1 A, B = rfwt(X[:m]), rfwt(X[m:]) for i in xrange(m): A.append(B[i] - A[i]) return A def cnt(x): r = 0 while x: x ^= x & (-x) r += 1 return r def add(A, B): return [A[x] + B[x] for x in xrange(len(A))] def mul(A, B): return [A[x] * B[x] for x in xrange(len(A))] def show(X): print ' '.join(map(str, X)) k = cnt(n - 1) + 1 F = [[0] * n for i in xrange(k)] G = [[0] * n for i in xrange(k)] H = [[0] * n for i in xrange(k)] # 按照大小分组 for i in xrange(n): c = cnt(i) F[c][i] = f[i] G[c][i] = g[i] # 提前进行 FWT for i in xrange(k): F[i] = fwt(F[i]) G[i] = fwt(G[i]) # 卷积部分 for i in xrange(k): for j in xrange(k - i): print i, j H[i + j] = add(H[i + j], mul(F[i], G[j])) for i in xrange(k): H[i] = rfwt(H[i]) # 计算结果 R = [0] * n for i in xrange(n): c = cnt(i) R[i] = H[c][i] show(R) |