快速沃尔什变换
简介
沃尔什转换(Walsh Transform)是在频谱分析上作为离散傅立叶变换的替代方案的一种方法,在信号处理中应用很广泛。FFT 是 double 类型的,但是 Walsh 把信号在不同震荡频率方波下拆解,因此所有的系数都是绝对值大小相同的整数,这使得不需要作浮点数的乘法运算,提高了运算速度。
所以,FWT 和 FFT 的核心思想应该是相同的,都是对数组的变换。我们记对数组 𝐴 进行快速沃尔什变换后得到的结果为 𝐹𝑊𝑇[𝐴]
 进行快速沃尔什变换后得到的结果为 𝐹𝑊𝑇[𝐴]![FWT[A]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 。
。
那么 FWT 核心思想就是:
我们需要一个新序列 𝐶 ,由序列 𝐴
,由序列 𝐴 和序列 𝐵
 和序列 𝐵 经过某运算规则得到,即 𝐶 =𝐴 ⋅𝐵
 经过某运算规则得到,即 𝐶 =𝐴 ⋅𝐵 ;
;
我们先正向得到序列 𝐹𝑊𝑇[𝐴],𝐹𝑊𝑇[𝐵]![FWT[A], FWT[B]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ,再根据 𝐹𝑊𝑇[𝐶] =𝐹𝑊𝑇[𝐴] ⋅𝐹𝑊𝑇[𝐵]
,再根据 𝐹𝑊𝑇[𝐶] =𝐹𝑊𝑇[𝐴] ⋅𝐹𝑊𝑇[𝐵]![FWT[C]=FWT[A] \cdot FWT[B]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 在 𝑂(𝑛)
 在 𝑂(𝑛) 的时间复杂度内求出 𝐹𝑊𝑇[𝐶]
 的时间复杂度内求出 𝐹𝑊𝑇[𝐶]![FWT[C]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ,其中 ⋅
,其中 ⋅ 是序列对应位置相乘;
 是序列对应位置相乘;
然后逆向运算得到原序列 𝐶 。时间复杂度为 𝑂(𝑛log𝑛)
。时间复杂度为 𝑂(𝑛log𝑛) 。
。
在算法竞赛中,FWT 是用于解决对下标进行位运算卷积问题的方法。
公式:𝐶𝑖 =∑𝑖=𝑗⊕𝑘𝐴𝑗𝐵𝑘
(其中 ⊕ 是二元位运算中的某一种)
 是二元位运算中的某一种)
下面我们举 ∪ (按位或)、∩
(按位或)、∩ (按位与)和 ⊕
(按位与)和 ⊕ (按位异或)为例。
(按位异或)为例。
FWT 的运算
或运算
如果有 𝑘 =𝑖 ∪𝑗 ,那么 𝑖
,那么 𝑖 的二进制位为 1
 的二进制位为 1 的位置和 𝑗
 的位置和 𝑗 的二进制位为 1
 的二进制位为 1 的位置肯定是 𝑘
 的位置肯定是 𝑘 的二进制位为 1
 的二进制位为 1 的位置的子集。
 的位置的子集。
现在要得到 𝐹𝑊𝑇[𝐶] =𝐹𝑊𝑇[𝐴] ⋅𝐹𝑊𝑇[𝐵]![FWT[C] = FWT[A] \cdot FWT[B]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ,我们就要构造这个 FWT 的规则。
,我们就要构造这个 FWT 的规则。
我们按照定义,显然可以构造 𝐹𝑊𝑇[𝐴]𝑖 =𝐴′𝑖 =∑𝑖=𝑖∪𝑗𝐴𝑗![FWT[A]_i = A'_i = \sum_{i=i\cup j}A_{j}](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ,来表示 𝑗
,来表示 𝑗 满足二进制中 1
 满足二进制中 1 为 𝑖
 为 𝑖 的子集。
 的子集。
那么有:
𝐹𝑊𝑇[𝐴]𝑖⋅𝐹𝑊𝑇[𝐵]𝑖=(∑𝑖∪𝑗=𝑖𝐴𝑗)(∑𝑖∪𝑘=𝑖𝐵𝑘)=∑𝑖∪𝑗=𝑖∑𝑖∪𝑘=𝑖𝐴𝑗𝐵𝑘=∑𝑖∪(𝑗∪𝑘)=𝑖𝐴𝑗𝐵𝑘=𝐹𝑊𝑇[𝐶]𝑖![\begin{aligned}
FWT[A]_i\cdot FWT[B]_i&=\left(\sum_{i\cup j=i} A_j\right)\left(\sum_{i\cup k=i} B_k\right) \\
&=\sum_{i\cup j=i}\sum_{i\cup k=i}A_jB_k \\
&=\sum_{i\cup(j\cup k)=i}A_jB_k \\
&= FWT[C]_i
\end{aligned}](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
那么我们接下来看 𝐹𝑊𝑇[𝐴]![FWT[A]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 怎么求。
 怎么求。
首先肯定不能枚举了,复杂度为 𝑂(𝑛2) 。既然不能整体枚举,我们就考虑分治。
。既然不能整体枚举,我们就考虑分治。
我们把整个区间二分,其实二分区间之后,下标写成二进制形式是有规律可循的。
我们令 𝐴0 表示 𝐴
 表示 𝐴 的前一半,𝐴1
 的前一半,𝐴1 表示区间的后一半,那么 𝐴0
 表示区间的后一半,那么 𝐴0 就是 A 下标最大值的最高位为 0
 就是 A 下标最大值的最高位为 0 ,他的子集就是他本身的子集(因为最高位为 0
,他的子集就是他本身的子集(因为最高位为 0 了),但是 𝐴1
 了),但是 𝐴1 的最高位是 1
 的最高位是 1 ,他满足条件的子集不仅仅是他本身,还包最高位为 0
,他满足条件的子集不仅仅是他本身,还包最高位为 0 的子集,即
 的子集,即
𝐹𝑊𝑇[𝐴]=𝑚𝑒𝑟𝑔𝑒(𝐹𝑊𝑇[𝐴0],𝐹𝑊𝑇[𝐴0]+𝐹𝑊𝑇[𝐴1])![FWT[A] = merge(FWT[A_0], FWT[A_0] + FWT[A_1])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
其中 merge 表示像字符串拼接一样把两个数组拼起来,+ 就是普通加法,表示对应二进制位相加。
 就是普通加法,表示对应二进制位相加。
这样我们就通过二分能在 𝑂(log𝑛) 的时间复杂度内完成拼接,每次拼接的时候要完成一次运算,也就是说在 𝑂(𝑛log𝑛)
 的时间复杂度内完成拼接,每次拼接的时候要完成一次运算,也就是说在 𝑂(𝑛log𝑛) 的时间复杂度得到了 𝐹𝑊𝑇[𝐴]
 的时间复杂度得到了 𝐹𝑊𝑇[𝐴]![FWT[A]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 。
。
接下来就是反演了,其实反演是很简单的,既然知道了 𝐴0 的本身的子集是他自己(𝐴0 =𝐹𝑊𝑇[𝐴0]
 的本身的子集是他自己(𝐴0 =𝐹𝑊𝑇[𝐴0]![A_0 = FWT[A_0]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ),𝐴1
),𝐴1 的子集是 𝐹𝑊𝑇[𝐴0] +𝐹𝑊𝑇[𝐴1]
 的子集是 𝐹𝑊𝑇[𝐴0] +𝐹𝑊𝑇[𝐴1]![FWT[A_0] + FWT[A_1]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ,那就很简单的得出反演的递推式了:
,那就很简单的得出反演的递推式了:
𝑈𝐹𝑊𝑇[𝐴′]=𝑚𝑒𝑟𝑔𝑒(𝑈𝐹𝑊𝑇[𝐴′0],𝑈𝐹𝑊𝑇[𝐴′1]−𝑈𝐹𝑊𝑇[𝐴′0])![UFWT[A'] = merge(UFWT[A_0'], UFWT[A_1'] - UFWT[A_0'])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
下面我们给出代码实现。容易发现顺变换和逆变换可以合并为一个函数,顺变换时 type =1 ,逆变换时 type = −1
,逆变换时 type = −1 。
。
实现
 |  | void Or(ll *a, ll type) {  // 迭代实现,常数更小
  for (ll x = 2; x <= n; x <<= 1) {
    ll k = x >> 1;
    for (ll i = 0; i < n; i += x) {
      for (ll j = 0; j < k; j++) {
        (a[i + j + k] += a[i + j] * type) %= P;
      }
    }
  }
}
 | 
与运算
与运算类比或运算可以得到类似结论
𝐹𝑊𝑇[𝐴]=𝑚𝑒𝑟𝑔𝑒(𝐹𝑊𝑇[𝐴0]+𝐹𝑊𝑇[𝐴1],𝐹𝑊𝑇[𝐴1])![FWT[A] = merge(FWT[A_0] + FWT[A_1], FWT[A_1])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 𝑈𝐹𝑊𝑇[𝐴′]=𝑚𝑒𝑟𝑔𝑒(𝑈𝐹𝑊𝑇[𝐴′0]−𝑈𝐹𝑊𝑇[𝐴′1],𝑈𝐹𝑊𝑇[𝐴′1])
𝑈𝐹𝑊𝑇[𝐴′]=𝑚𝑒𝑟𝑔𝑒(𝑈𝐹𝑊𝑇[𝐴′0]−𝑈𝐹𝑊𝑇[𝐴′1],𝑈𝐹𝑊𝑇[𝐴′1])![UFWT[A'] = merge(UFWT[A_0'] - UFWT[A_1'], UFWT[A_1'])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
下面我们给出代码实现。顺变换时 type =1 ,逆变换时 type = −1
,逆变换时 type = −1 。
。
实现
 |  | void And(ll *a, ll type) {
  for (ll x = 2; x <= n; x <<= 1) {
    ll k = x >> 1;
    for (ll i = 0; i < n; i += x) {
      for (ll j = 0; j < k; j++) {
        (a[i + j] += a[i + j + k] * type) %= P;
      }
    }
  }
}
 | 
异或运算
异或的卷积是基于如下原理:
若我们令 𝑥 ∘𝑦 表示 𝑥 ∩𝑦
 表示 𝑥 ∩𝑦 中 1
 中 1 数量的奇偶性,即 𝑥 ∘𝑦 =popcnt(𝑥 ∩𝑦)mod2
 数量的奇偶性,即 𝑥 ∘𝑦 =popcnt(𝑥 ∩𝑦)mod2 ,那么容易有 (𝑥 ∘𝑦) ⊕(𝑥 ∘𝑧) =𝑥 ∘(𝑦 ⊕𝑧)
,那么容易有 (𝑥 ∘𝑦) ⊕(𝑥 ∘𝑧) =𝑥 ∘(𝑦 ⊕𝑧) 。
。
对于 𝐹𝑊𝑇[𝐴]![FWT[A]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的运算其实也很好得到。
 的运算其实也很好得到。
设 𝐹𝑊𝑇[𝐴]𝑖 =∑𝑖∘𝑗=0𝐴𝑗 −∑𝑖∘𝑗=1𝐴𝑗![FWT[A]_i=\sum_{i\circ j=0}A_j-\sum_{i\circ j=1}A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 。我们来证一下 𝐹𝑊𝑇[𝐶] =𝐹𝑊𝑇[𝐴] ⋅𝐹𝑊𝑇[𝐵]
。我们来证一下 𝐹𝑊𝑇[𝐶] =𝐹𝑊𝑇[𝐴] ⋅𝐹𝑊𝑇[𝐵]![FWT[C] = FWT[A] \cdot FWT[B]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的正确性:
 的正确性:
𝐹𝑊𝑇[𝐴]𝑖𝐹𝑊𝑇[𝐵]𝑖=(∑𝑖∘𝑗=0𝐴𝑗−∑𝑖∘𝑗=1𝐴𝑗)(∑𝑖∘𝑘=0𝐵𝑘−∑𝑖∘𝑘=1𝐵𝑘)=(∑𝑖∘𝑗=0𝐴𝑗∑𝑖∘𝑘=0𝐵𝑘+∑𝑖∘𝑗=1𝐴𝑗∑𝑖∘𝑘=1𝐵𝑘)−(∑𝑖∘𝑗=0𝐴𝑗∑𝑖∘𝑘=1𝐵𝑘+∑𝑖∘𝑗=1𝐴𝑗∑𝑖∘𝑘=0𝐵𝑘)=∑(𝑗⊕𝑘)∘𝑖=0𝐴𝑗𝐵𝑘−∑(𝑗⊕𝑘)∘𝑖=1𝐴𝑗𝐵𝑘=𝐹𝑊𝑇[𝐶]𝑖![\begin{aligned}
FWT[A]_iFWT[B]_i&=\left(\sum_{i\circ j=0}A_j-\sum_{i\circ j=1}A_j\right)\left(\sum_{i\circ k=0}B_k-\sum_{i\circ k=1}B_k\right) \\
&=\left(\sum_{i\circ j=0}A_j\sum_{i\circ k=0}B_k+\sum_{i\circ j=1}A_j\sum_{i\circ k=1}B_k\right)-\left(\sum_{i\circ j=0}A_j\sum_{i\circ k=1}B_k+\sum_{i\circ j=1}A_j\sum_{i\circ k=0}B_k\right) \\
&=\sum_{(j\oplus k)\circ i=0}A_jB_k-\sum_{(j\oplus k)\circ i=1}A_jB_k \\
&=FWT[C]_i
\end{aligned}](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
来看看怎么快速计算 𝐴,𝐵 的值,依旧是分治:
 的值,依旧是分治:
对于 𝑖 在当前位为 0
 在当前位为 0 的子数列 𝐹𝑊𝑇[𝐴0]
 的子数列 𝐹𝑊𝑇[𝐴0]![FWT[A_0]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ,进行 ∘
,进行 ∘ 运算时发现它和 0
 运算时发现它和 0 计算或和 1
 计算或和 1 计算结果都不会变(因为 0 ∩0 =0,0 ∩1 =0
 计算结果都不会变(因为 0 ∩0 =0,0 ∩1 =0 ),所以 𝐹𝑊𝑇[𝐴] =∑𝑖∘𝑗=0𝐴𝑗 −∑𝑖∘𝑗=1𝐴𝑗
),所以 𝐹𝑊𝑇[𝐴] =∑𝑖∘𝑗=0𝐴𝑗 −∑𝑖∘𝑗=1𝐴𝑗![FWT[A]=\sum_{i\circ j=0}A_j-\sum_{i\circ j=1}A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 中的 ∑𝑖∘𝑗=1𝐴𝑗 =0
 中的 ∑𝑖∘𝑗=1𝐴𝑗 =0 。
。
对于 𝑖 在当前位为 1
 在当前位为 1 的子数列 𝐴1
 的子数列 𝐴1 ,进行 ∘
,进行 ∘ 运算时发现它和 0
 运算时发现它和 0 计算结果是 0
 计算结果是 0 ,和 1
,和 1 计算结果是 1
 计算结果是 1 (因为 1 ∩0 =0,1 ∩1 =1
(因为 1 ∩0 =0,1 ∩1 =1 )。
)。
综上,有:
𝐹𝑊𝑇[𝐴]=𝑚𝑒𝑟𝑔𝑒((𝐹𝑊𝑇[𝐴0]+𝐹𝑊𝑇[𝐴1])−0,𝐹𝑊𝑇[𝐴0]−𝐹𝑊𝑇[𝐴1])![FWT[A]=merge((FWT[A_0]+FWT[A_1])-0, FWT[A_0]-FWT[A_1])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
也就是:
𝐹𝑊𝑇[𝐴]=𝑚𝑒𝑟𝑔𝑒(𝐹𝑊𝑇[𝐴0]+𝐹𝑊𝑇[𝐴1],𝐹𝑊𝑇[𝐴0]−𝐹𝑊𝑇[𝐴1])![FWT[A] = merge(FWT[A_0] + FWT[A_1], FWT[A_0] - FWT[A_1])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
逆变换易得:
𝑈𝐹𝑊𝑇[𝐴′]=𝑚𝑒𝑟𝑔𝑒(𝑈𝐹𝑊𝑇[𝐴′0]+𝑈𝐹𝑊𝑇[𝐴′1]2,𝑈𝐹𝑊𝑇[𝐴′0]−𝑈𝐹𝑊𝑇[𝐴′1]2)![UFWT[A'] = merge(\frac{UFWT[A_0'] + UFWT[A_1']}{2}, \frac{UFWT[A_0'] - UFWT[A_1']}{2})](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
给出代码,顺变换时 type =1 ,逆变换时 type =12
,逆变换时 type =12 。
。
实现
 |  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13 | void Xor(ll *a, ll type) {
  for (ll x = 2; x <= n; x <<= 1) {
    ll k = x >> 1;
    for (ll i = 0; i < n; i += x) {
      for (ll j = 0; j < k; j++) {
        (a[i + j] += a[i + j + k]) %= P;
        (a[i + j + k] = a[i + j] - a[i + j + k] * 2) %= P;
        (a[i + j] *= type) %= P;
        (a[i + j + k] *= type) %= P;
      }
    }
  }
}
 | 
同或运算
类比异或运算给出公式:
𝐹𝑊𝑇[𝐴]𝑖 =∑𝐶1𝐴𝑗 −∑𝐶2𝐴𝑗![FWT[A]_{i} = \sum_{C_1}A_{j} - \sum_{C_2}A_{j}](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) (𝐶1
(𝐶1 表示 popcnt(𝑥 ∪𝑦)mod2
 表示 popcnt(𝑥 ∪𝑦)mod2 为 0
 为 0 ,𝐶2
,𝐶2 表示 popcnt(𝑥 ∪𝑦)mod2
 表示 popcnt(𝑥 ∪𝑦)mod2 为 1
 为 1 )
)
𝐹𝑊𝑇[𝐴]=𝑚𝑒𝑟𝑔𝑒(𝐹𝑊𝑇[𝐴1]−𝐹𝑊𝑇[𝐴0],𝐹𝑊𝑇[𝐴1]+𝐹𝑊𝑇[𝐴0])![FWT[A] = merge(FWT[A_1] - FWT[A_0], FWT[A_1] + FWT[A_0])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 𝑈𝐹𝑊𝑇[𝐴′]=𝑚𝑒𝑟𝑔𝑒(𝑈𝐹𝑊𝑇[𝐴′1]−𝑈𝐹𝑊𝑇[𝐴′0]2,𝑈𝐹𝑊𝑇[𝐴′1]+𝑈𝐹𝑊𝑇[𝐴′0]2)
𝑈𝐹𝑊𝑇[𝐴′]=𝑚𝑒𝑟𝑔𝑒(𝑈𝐹𝑊𝑇[𝐴′1]−𝑈𝐹𝑊𝑇[𝐴′0]2,𝑈𝐹𝑊𝑇[𝐴′1]+𝑈𝐹𝑊𝑇[𝐴′0]2)![UFWT[A'] = merge(\frac{UFWT[A_1'] - UFWT[A_0']}{2}, \frac{UFWT[A_1'] + UFWT[A_0']}{2})](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
另一个角度的 FWT
我们设 𝑐(𝑖,𝑗) 是 𝐴𝑗
 是 𝐴𝑗 对 𝐹𝑊𝑇[𝐴]𝑖
 对 𝐹𝑊𝑇[𝐴]𝑖![FWT[A]_i](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 的贡献系数。我们可以重新描述 FWT 变换的过程:
 的贡献系数。我们可以重新描述 FWT 变换的过程:
𝐹𝑊𝑇[𝐴]𝑖=𝑛−1∑𝑗=0𝑐(𝑖,𝑗)𝐴𝑗![FWT[A]_i = \sum_{j=0}^{n-1} c(i,j) A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
因为有:
𝐹𝑊𝑇[𝐴]𝑖⋅𝐹𝑊𝑇[𝐵]𝑖=𝐹𝑊𝑇[𝐶]𝑖![FWT[A]_i\cdot FWT[B]_i=FWT[C]_i](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
所以我们可以通过简单的证明得到:𝑐(𝑖,𝑗)𝑐(𝑖,𝑘) =𝑐(𝑖,𝑗 ⊙𝑘) 。其中 ⊙
。其中 ⊙ 是任意一种位运算。
 是任意一种位运算。
同时,𝑐 函数还有一个重要的性质,它可以按位处理。
 函数还有一个重要的性质,它可以按位处理。
举个例子,我们变换的时候:
𝐹𝑊𝑇[𝐴]𝑖=𝑛−1∑𝑗=0𝑐(𝑖,𝑗)𝐴𝑗![FWT[A]_i = \sum_{j=0}^{n-1} c(i,j) A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
这么做是比较劣的,我们将其拆分:
𝐹𝑊𝑇[𝐴]𝑖=𝑛/2−1∑𝑗=0𝑐(𝑖,𝑗)𝐴𝑗+𝑛−1∑𝑗=𝑛/2𝑐(𝑖,𝑗)𝐴𝑗![FWT[A]_i = \sum_{j=0}^{n/2-1} c(i,j) A_j+\sum_{j=n/2}^{n-1} c(i,j) A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
考虑前面的式子和后面的式子 𝑖,𝑗 的区别,发现只有最高位不同。
 的区别,发现只有最高位不同。
所以我们将 𝑖,𝑗 去除最高位的值为 𝑖′,𝑗′
 去除最高位的值为 𝑖′,𝑗′ ,并记 𝑖0
,并记 𝑖0 为 𝑖
 为 𝑖 的最高位。有:
 的最高位。有:
𝐹𝑊𝑇[𝐴]𝑖=𝑐(𝑖0,0)𝑛/2−1∑𝑗=0𝑐(𝑖′,𝑗′)𝐴𝑗+𝑐(𝑖0,1)𝑛−1∑𝑗=𝑛/2𝑐(𝑖′,𝑗′)𝐴𝑗![FWT[A]_i = c(i_0,0)\sum_{j=0}^{n/2-1} c(i',j') A_j+c(i_0,1)\sum_{j=n/2}^{n-1} c(i',j') A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
如果 𝑖0 =0 ,则有:
,则有:
𝐹𝑊𝑇[𝐴]𝑖=𝑐(0,0)𝑛/2−1∑𝑗=0𝑐(𝑖′,𝑗′)𝐴𝑗+𝑐(0,1)𝑛−1∑𝑗=𝑛/2𝑐(𝑖′,𝑗′)𝐴𝑗![FWT[A]_i = c(0,0)\sum_{j=0}^{n/2-1} c(i',j') A_j+c(0,1)\sum_{j=n/2}^{n-1} c(i',j') A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
𝑖0 =1 则有:
 则有:
𝐹𝑊𝑇[𝐴]𝑖=𝑐(1,0)𝑛/2−1∑𝑗=0𝑐(𝑖′,𝑗′)𝐴𝑗+𝑐(1,1)𝑛−1∑𝑗=𝑛/2𝑐(𝑖′,𝑗′)𝐴𝑗![FWT[A]_i = c(1,0)\sum_{j=0}^{n/2-1} c(i',j') A_j+c(1,1)\sum_{j=n/2}^{n-1} c(i',j') A_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
也就是说,我们只需要:
[𝑐(0,0)𝑐(0,1)𝑐(1,0)𝑐(1,1)]
四个数就可以完成变换了。我们称这个矩阵为位矩阵。
如果我们要进行逆变换,则需要上面的位矩阵的逆矩阵。
若逆矩阵为 𝑐−1 ,可以通过类似操作得到原数:
,可以通过类似操作得到原数:
𝐴𝑖=𝑛∑𝑗=0𝑐−1(𝑖,𝑗)𝐹𝑊𝑇[𝐴]𝑗![A_i = \sum_{j=0}^n c^{-1}(i,j) FWT[A]_j](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
逆矩阵不一定存在,比如如果有一排 0 或者一列 0
 或者一列 0 那么这个矩阵就没有逆,我们在构造时需要格外小心。
 那么这个矩阵就没有逆,我们在构造时需要格外小心。
按位或
我们可以构造:
[1011]
这样满足 𝑐(𝑖,𝑗)𝑐(𝑖,𝑘) =𝑐(𝑖,𝑗 ∪𝑘) 。我们发现,这和我们前面推出的 𝐹𝑊𝑇[𝐴] =merge(𝐹𝑊𝑇[𝐴0],𝐹𝑊𝑇[𝐴0] +𝐹𝑊𝑇[𝐴1])
。我们发现,这和我们前面推出的 𝐹𝑊𝑇[𝐴] =merge(𝐹𝑊𝑇[𝐴0],𝐹𝑊𝑇[𝐴0] +𝐹𝑊𝑇[𝐴1])![FWT[A]=\text{merge}(FWT[A_0], FWT[A_0]+FWT[A_1])](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 一模一样!同理,下面也是一个满足这个条件的矩阵,但我们一般使用上面这个:
 一模一样!同理,下面也是一个满足这个条件的矩阵,但我们一般使用上面这个:
[1110]
虽然下面这个矩阵也满足 𝑐(𝑖,𝑗)𝑐(𝑖,𝑘) =𝑐(𝑖,𝑗 ∪𝑘) ,但这个矩阵存在一排 0
,但这个矩阵存在一排 0 ,不存在逆,所以不合法:
,不存在逆,所以不合法:
[0011]
如果我们要进行逆变换,则需要对矩阵求逆,以 最上面 这个矩阵为例,得:
[10−11]
然后按照顺变换的方法,把逆变换矩阵代入即可。
按位与
我们可以构造:
[1101]
这样满足 𝑐(𝑖,𝑗)𝑐(𝑖,𝑘) =𝑐(𝑖,𝑗 ∩𝑘) 。
。
逆矩阵:
[1−101]
按位异或
我们可以构造:
[111−1]
这样满足 𝑐(𝑖,𝑗)𝑐(𝑖,𝑘) =𝑐(𝑖,𝑗 ⊕𝑘) 。
。
逆矩阵:
[0.50.50.5−0.5]
FWT 是线性变换
FWT 是线性变换。也就是说,它满足:
𝐹𝑊𝑇[𝐴+𝐵]=𝐹𝑊𝑇[𝐴]+𝐹𝑊𝑇[𝐵]![FWT[A+B]=FWT[A]+FWT[B]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
以及:
𝐹𝑊𝑇[𝑐⋅𝐴]=𝑐⋅𝐹𝑊𝑇[𝐴]![FWT[c\cdot A]=c\cdot FWT[A]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)
K 维 FWT
其实位运算的本质是对一个 𝑛 维 {0,1}
 维 {0,1} 向量的运算。或运算就是每一维取 max
 向量的运算。或运算就是每一维取 max 。且运算就是每一维取 min
。且运算就是每一维取 min 。异或运算则是每一维对应相加再 mod2
。异或运算则是每一维对应相加再 mod2 。
。
位运算有个特点:向量的每一位都是独立的。
我们把 {0,1} 扩展到 [0,𝐾) ∩𝐙
 扩展到 [0,𝐾) ∩𝐙 也就是扩展到 𝐾
 也就是扩展到 𝐾 进制,看看会得到什么?
 进制,看看会得到什么?
max 运算
我们将 ∪ 运算拓展到 𝐾
 运算拓展到 𝐾 进制,定义 𝑖 ∪𝑗
 进制,定义 𝑖 ∪𝑗 表示按位取 max
 表示按位取 max ,有:
,有:
𝑐(𝑖,𝑗)𝑐(𝑖,𝑘)=𝑐(𝑖,𝑗∪𝑘)
若 𝑗 =𝑘 ,那么上式又是:
,那么上式又是:
𝑐(𝑖,𝑗)𝑐(𝑖,𝑗)=𝑐(𝑖,𝑗)
也就是说,每一行的 1 必定只能在 0
 必定只能在 0 的前面,如果在后面则不合法了。手玩一下可以发现一组合法构造:
 的前面,如果在后面则不合法了。手玩一下可以发现一组合法构造:
⎡⎢ ⎢ ⎢ ⎢⎣1000110011101111⎤⎥ ⎥ ⎥ ⎥⎦
求逆可得:
⎡⎢ ⎢ ⎢ ⎢⎣1000−11000−11000−11⎤⎥ ⎥ ⎥ ⎥⎦
min 运算
我们将 ∩ 运算拓展到 𝐾
 运算拓展到 𝐾 进制,定义 𝑖 ∩𝑗
 进制,定义 𝑖 ∩𝑗 表示按位取 min
 表示按位取 min ,有:
,有:
𝑐(𝑖,𝑗)𝑐(𝑖,𝑘)=𝑐(𝑖,𝑗∩𝑘)
若 𝑗 =𝑘 ,那么上式又是:
,那么上式又是:
𝑐(𝑖,𝑗)𝑐(𝑖,𝑗)=𝑐(𝑖,𝑗)
也就是说,每一行的 1 必定只能在 0
 必定只能在 0 的后面,如果在前面则不合法了。手玩一下可以发现一组合法构造:
 的后面,如果在前面则不合法了。手玩一下可以发现一组合法构造:
⎡⎢ ⎢ ⎢ ⎢⎣1111011100110001⎤⎥ ⎥ ⎥ ⎥⎦
求逆可得:
⎡⎢ ⎢ ⎢ ⎢⎣1−10001−10001−10001⎤⎥ ⎥ ⎥ ⎥⎦
前两者用得比较少,用得比较多的是:
不进位加法
我们将 ⊕ 运算拓展到 𝐾
 运算拓展到 𝐾 进制,定义 𝑖 ⊕𝑗
 进制,定义 𝑖 ⊕𝑗 表示按位相加再 mod𝐾
 表示按位相加再 mod𝐾 ,有:
,有:
𝑐(𝑖,𝑗)𝑐(𝑖,𝑘)=𝑐(𝑖,𝑗⊕𝑘)
我们构造 𝑐(𝑖,𝑗) =𝜔𝑗𝐾 ,就可以满足要求了:
,就可以满足要求了:
𝜔𝑗𝐾𝜔𝑘𝐾=𝜔𝑗⊕𝑘𝐾
但是每一行都一样矩阵也没有逆,所以我们可以构造 𝑐(𝑖,𝑗) =𝜔(𝑖−1)𝑗𝐾 即可。
 即可。
有下面这个矩阵:
⎡⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢⎣111⋯11𝜔1𝐾𝜔2𝐾⋯𝜔𝑘−1𝐾1𝜔2𝐾𝜔4𝐾⋯𝜔2(𝑘−1)𝐾1𝜔3𝐾𝜔6𝐾⋯𝜔3(𝑘−1)𝐾⋮⋮⋮⋱⋮1𝜔𝑘−1𝐾𝜔2(𝑘−1)𝐾⋯𝜔(𝑘−1)(𝑘−1)𝐾⎤⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥⎦
此即为 范德蒙德矩阵,求逆可得:
1𝐾⎡⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢⎣111⋯11𝜔−1𝐾𝜔−2𝐾⋯𝜔−(𝑘−1)𝐾1𝜔−2𝐾𝜔−4𝐾⋯𝜔−2(𝑘−1)𝐾1𝜔−3𝐾𝜔−6𝐾⋯𝜔−3(𝑘−1)𝐾⋮⋮⋮⋱⋮1𝜔−(𝑘−1)𝐾𝜔−2(𝑘−1)𝐾⋯𝜔−(𝑘−1)(𝑘−1)𝐾⎤⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥⎦
如果我们题目给出的模数是存在单位根的,我们就可以简单实现。
但是 单位根在模意义下可能不存在,所以我们考虑扩域,就是人为地定义一个 𝑥 ,满足 𝑥𝐾 =1
,满足 𝑥𝐾 =1 ,然后直接把 𝑥
,然后直接把 𝑥 代入计算,这样每个数都是一个关于 𝑥
 代入计算,这样每个数都是一个关于 𝑥 的 𝑘 −1
 的 𝑘 −1 次多项式。我们只需要在 mod𝑥𝐾−1
 次多项式。我们只需要在 mod𝑥𝐾−1 下计算即可。那么矩阵可以这么表示:
 下计算即可。那么矩阵可以这么表示:
⎡⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢⎣111⋯11𝑥1𝑥2⋯𝑥𝑘−11𝑥2𝑥4⋯𝑥2(𝑘−1)1𝑥3𝑥6⋯𝑥3(𝑘−1)⋮⋮⋮⋱⋮1𝑥𝑘−1𝑥2(𝑘−1)⋯𝑥(𝑘−1)(𝑘−1)⎤⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥⎦
但是这么做可能会存在零因子,也就是 一个数有多种表示方法,我们无法确定一个数的真实值。
我们考虑不 mod𝑥𝐾−1 了,我们 mod
 了,我们 mod 分圆多项式 Φ𝐾(𝑥)
 分圆多项式 Φ𝐾(𝑥) ,他满足 𝑥
,他满足 𝑥 的阶为 𝑘
 的阶为 𝑘 ,且在 ℚ
,且在 ℚ 上不可约。所以我们定义上面的计算是在 modΦ𝐾(𝑥)
 上不可约。所以我们定义上面的计算是在 modΦ𝐾(𝑥) 下进行即可。
 下进行即可。
还有一个问题是,modΦ𝐾(𝑥) 常数大(因为 Φ
 常数大(因为 Φ 本身就是一个多项式)。但是因为 Φ𝐾(𝑥) ∣𝑥𝑘 −1
 本身就是一个多项式)。但是因为 Φ𝐾(𝑥) ∣𝑥𝑘 −1 ,我们只需要在计算时 mod𝑥𝑘 −1
,我们只需要在计算时 mod𝑥𝑘 −1 ,最后再 modΦ𝐾(𝑥)
,最后再 modΦ𝐾(𝑥) 即可。
 即可。
例题
「CF 1103E」Radix sum
 给定一个长度为 𝑛 的序列 𝑎1,𝑎2,...,𝑎𝑛
 的序列 𝑎1,𝑎2,...,𝑎𝑛 ,对于每一个 𝑝 ∈[0,𝑛 −1]
,对于每一个 𝑝 ∈[0,𝑛 −1]![p \in [0,n-1]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ,求满足下列条件的整数序列 𝑖1,𝑖2,...,𝑖𝑛
,求满足下列条件的整数序列 𝑖1,𝑖2,...,𝑖𝑛 的方案数,对 258
 的方案数,对 258 取模:
 取模:
 - ∀𝑗 ∈[1,𝑛],𝑖𝑗 ∈[1,𝑛]![\forall j \in [1,n] , i_j \in [1,n]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) ; ;
- 𝑛∑𝑗=1𝑎𝑖𝑗 =𝑝 ,这里的加法定义为十进制不进位加法。 ,这里的加法定义为十进制不进位加法。
𝑛 ≤105,𝑎𝑖 ≤105
 题解
 我们可以想到 dp:设计状态 𝑓𝑖,𝑠 表示考虑到第 𝑖
 表示考虑到第 𝑖 个数,当前加法状态为 𝑠
 个数,当前加法状态为 𝑠 。因为 FWT 变换是线性的,可以先变换为 FWT 点值表示法,然后变成自己的 𝑛
。因为 FWT 变换是线性的,可以先变换为 FWT 点值表示法,然后变成自己的 𝑛 次幂,最后再变换回来。
 次幂,最后再变换回来。
 上面是平凡的,但是题目给出了模数 258 。发现没有单位根,所以考虑扩域。
。发现没有单位根,所以考虑扩域。
 这里的分圆多项式 Φ10(𝑥) =𝑥4 −𝑥3 +𝑥2 −𝑥 +1 。
。
 然而我们发现 UFWT 时,需要除去进制 10 ,然而我们发现 10
,然而我们发现 10 在 258
 在 258 下没有逆元。实际上我们发现 5
 下没有逆元。实际上我们发现 5 在 258
 在 258 下是有逆元的:57646075230342349
 下是有逆元的:57646075230342349 ,我们只需要再除去一个 2
,我们只需要再除去一个 2 就可以了。设已经除以了 5
 就可以了。设已经除以了 5 的答案为 𝑥
 的答案为 𝑥 ,真正的答案为 𝑦
,真正的答案为 𝑦 ,也就是 25𝑦 ≡𝑥(mod264)
,也就是 25𝑦 ≡𝑥(mod264) ,显然,我们有 𝑦 ≡𝑥25(mod264−5)
,显然,我们有 𝑦 ≡𝑥25(mod264−5) ,也就是 𝑦 ≡𝑥25(mod259)
,也就是 𝑦 ≡𝑥25(mod259) ,所以直接将最后的答案除以 25
,所以直接将最后的答案除以 25 即可。虽然出题人不知道为什么要模 258
 即可。虽然出题人不知道为什么要模 258 ,但再取下模即可。
,但再取下模即可。
【CF103329F】【XXII Opencup, Grand Prix of XiAn】The Struggle
 给出一个椭圆 𝐸 ,其中所有整点的坐标均在 [1,4 ⋅106]
,其中所有整点的坐标均在 [1,4 ⋅106]![[1,4 \cdot 10^6]](data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) 之间。求 ∑(𝑥,𝑦)∈𝐸(𝑥 ⊕𝑦)33𝑥−2𝑦−1mod  109 +7
 之间。求 ∑(𝑥,𝑦)∈𝐸(𝑥 ⊕𝑦)33𝑥−2𝑦−1mod  109 +7 的值。
 的值。
 题解
 这是一道比较不裸的题,出题人提供了详细的英文题解,具体请见 此链接。
参考资料
本页面最近更新:2025/9/22 01:44:35,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:Ir1d, Tiphereth-A, ZnPdCo, c-forrest, Enter-tainer, Xeonacid, BinDir0, HeRaNO, lxlonlyn, nocriz, nocrizwang, qkhm, TrisolarisHD, xyf007
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用