Poly
FFT & NTT 复习笔记
默认文中的形如 \([l,r)\) 的区间为其与整数集的交集。
快速变换
设原多项式为 \(F(x) = \sum_{i \in [0,n)} a_i x ^ i\),其中 \(n = 2 ^ k, k \in \mathbb Z ^ +\)。
我们要求 \(\forall i \in [0,n),\hat a_i = F(t_i)\),其中 \(t\) 是一个长度为 \(n\) 且两两互不相同的序列。
显然 \(F\) 可以被一组 \(\hat a,t\) 唯一确定,即点值表示法。
另设两个多项式
则有
考虑构造单位根 \(\omega _n ^k\) 满足 \(\omega _n ^{\frac n 2} = -1, \omega _{2n} ^ {2k} = \omega _n ^k\)。
显然也有 \(\omega _n ^n = \omega _n ^0 = 1\)。
设 \(\forall i \in [0,n), t_i = \omega _n ^i\)。
当 \(x = \omega _n ^k, k \in [0,\frac n 2)\) 时显然有
当 \(x = \omega _n ^{k + \frac n 2}, k \in [0,\frac n 2)\) 时有
由于两者只有一个符号的差异,于是 \(F\) 的点值可以直接 \(\mathrm O(n)\) 从 \(G_0, G_1\) 的点值得到。
递归解决,时间复杂度 \(\mathrm O(n \log n)\)。
逆变换
设变换后的点值序列为 \(\hat a\),即
设多项式 \(\hat F(x) = \sum _{i \in [0,n)} \hat a_i x^i\)。
对 \(\hat F\) 进行点值变换(\(\forall i \in [0,n),t_i = \omega _n ^{-i}\)),设点值序列为 \(s\)。
则有
显然第二个求和是一个等比数列,由等比数列求和公式 \(\sum _{i \in [m,n)} p^i = \frac {p^m - p^n} {1 - p}\) 得:
- 当 \(\omega _n ^{k-i} \not = 1 \iff i \not = k\)
- 当 \(\omega _n ^{k-i} = 1 \iff i = k\)
因此
于是我们有
构造单位根
- FFT
在复数域下,有 \(\omega _n = \cos \frac {2 \pi} n + \mathrm i \sin \frac {2 \pi} {n}\)。
其中 \(\mathrm i = \sqrt {-1}\) 是 虚数单位,可以用 C++
中的 complex
库中的 std::complex<double/long double>
存储复数。
- NTT
对于模数 \(P \in \mathbb P, \exists n,k \in \mathbb Z^+, P=2^nk+1\),在模 \(P\) 意义下有 \(\omega _n \equiv g ^ {\frac {P-1} n}\),其中 \(g\) 是原根。
\(g\) 是模 \(P\) 意义下的原根当且仅当 \(g ^i \not \equiv 1 \pmod P,\forall i \in [1,\phi(P))\) 且 \(g ^{\phi(P)} \equiv 1 \pmod P\)。
specially,\(\forall P \in \mathbb P\),其原根 \(g\) 满足 \(\forall i \in [1,P-1), g ^i \not \equiv 1 \pmod P\) 且 \(g^{P-1} \equiv 1 \pmod P\)。
于是对 \(n = 2 ^m, m \in \mathbb Z ^+\),我们有 \(\omega _n ^n \equiv g ^{\frac {P - 1} {n} \cdot n} \equiv g ^{P - 1} \equiv 1, \pmod P\),且 \(\omega _n ^{\frac n 2} \equiv g ^{\frac {P - 1} 2} \equiv \pm \sqrt {g ^ {P - 1}} \equiv \pm 1 \pmod P\),又 \(g ^ {\frac {P-1} 2} \not \equiv 1 \pmod P\),所以 \(\omega _n ^{\frac n 2} \equiv -1 \pmod P\)。
还有 \(\omega _{2n} ^{2k} \equiv g ^{\frac {2k(P-1)} {2n}} \equiv g ^{\frac{k(P-1)} n} \equiv \omega _n ^k \pmod P\)
由于原根的特殊性,模数 \(P \in \mathbb P\) 有特殊的限制,一般有 \(P = k 2 ^m + 1, k,m\in \mathbb Z ^+\)。
常见的模数有
\(\rm FWT\) (Fast Walsh Transform) 学习笔记
本文中使用 \(\cap\) 表示按位与,用 \(\cup\) 表示按位或
与/或 卷积
问题引入
给定长度为 \(2^n\) 的数列 \(A,B\),求 \(C_i = \sum_{j \cup k = i} A_j \times B_k\)
显然有 \(O(4^n)\) 的暴力
正变换
这一部分可以参考 FMT 中的 Zeta 变换 ,即定义 \(\hat A_i\) 为序列 \(A\) 中 \(i\) 的子集和
快速变换
考虑分治
对于长度为 \(2^n\) 的待求解的 \(\hat A\),我们把它分成独立的两部分,即 \([0,2^{n-1})\) 和 \([2^{n-1},2^n)\) 两个区间分别求解
然后考虑这两个区间之间的贡献,发现只有 \([0,2^{n-1})\) 对 \([2^{n-1},2^n)\) 有贡献,于是对于 \(i \in [0,2^{n-1})\),将 \(\hat A_{i}\) 加到 \(\hat A_{i+2^{n-1}}\) 中即可
时间复杂度 \(O(n 2^n)\)
逆变换
我们只需要对照正变换中的操作步骤,一步一步撤销变换即可
即对于 \(i \in [0,2^{n-1})\),将 \(\hat A_{i+2^{n-1}}\) 减去 \(\hat A_{i}\) 的贡献,然后再递归地求解
当然还有另外的做法,即我们要将长度为 \(2^n\) 的 \(\hat A_i\) 在全集为 \(2^n-1\) 的情况下(即不考虑目前的长度外的子集)只包括自己的 \(A_i\),那么我们递归地求解完左右两个区间后,肯定有 \(\forall i\in [0,2^{n-1}),\hat A_{i+2^{n-1}}=\hat A_i+A_{i+2^{n-1}}\),因此减去贡献即可
in brief,可以先递归再减贡献,这样逆变换与正变换只有一个 +/-
的变化
推广
对于 \(\cap\) 的情况,与 \(\cup\) 十分相似,请读者尽量自己构造变换,推一下式子,可以加深理解
如果没有思路,here
异或 卷积
问题引入
给定长度为 \(2^n\) 的数列 \(A,B\),求 \(C_i = \sum_{j \oplus k = i} A_j \times B_k\)
原理
设 \(\operatorname{popcnt}(i)\) 表示 \(i\) 在二进制下 1
的个数,则有
证明:显然 \(k\) 为 \(0\) 的位上,\(i,j\) 的取值不影响结果,那么设 \(i'=i\cap k,j'=j\cap k\),那么问题转化为 $$ \operatorname{popcnt}(i') + \operatorname{popcnt}(j') \equiv \operatorname{popcnt}(i'\oplus j') \pmod 2 $$ 对于 \(i',j'\) 每一位分开讨论,原命题易证
即异或不会改变 \(1\) 的总数的奇偶性
正变换
我们尝试构造一个变换 $$ \hat A_i = \sum_j g(i,j) A_j $$ 例如对于 \(\cup\) 卷积 ,\(g(i,j)=[i\cup j=i]\)
这个 \(g\) 函数满足以下性质:
即我们要使得 \(g(i,j)\times g(i,k) = g(i,j \oplus k)\)
因为 \(\operatorname{popcnt}(i \cap k) + \operatorname{popcnt}(j \cap k) \equiv \operatorname{popcnt}((i\oplus j)\cap k) \pmod 2\),我们发现 \(g(i,j) = (-1)^{\operatorname{popcnt}(i \cap j)}\) 满足这个性质
手动推一下式子:
快速变换
有点抽象,画个 \(n=3\) 的图来模拟一下
假设现在我们要考虑从 \(n=2\) 到 \(n=3\) 的变换,我们发现左边是 0??
,右边是 1??
,分别多出一个最高位
设原变换为 \(F_i\) ,目标变换为 \(G_i\)
- 左 \(\to\) 左
我们发现左边内部之间的 \(\cap\) 结果不变,因此 \(\forall i<4, G_i \gets F_i\)
- 左 \(\to\) 右 / 右 \(\to\) 左
我们发现 0??
\(\cap\) 1??
= 0??
,因此其 1
的个数不变,因此 \(\forall i<4,G_i \gets F_{i+4}, G_{i+4} \gets F_i\)。
- 右 \(\to\) 右
我们发现之前是 ??
\(\cap\) ??
= ??
,但是现在在前面加了一个 1
,因此 \(-1\) 的指数加一,所以 \(\forall i<4,G_{i+4} \gets -F_{i+4}\)
综上,我们有 \(\forall i<4,G_i=F_i+F_{i+4}, G_{i+4}=F_i-F_{i+4}\)
generally,对于从 \(n-1\) 到 \(n\) 的变换,我们有
时间复杂度 \(O(n2^n)\)。
逆变换
对照上面式子易得 (留给读者思考)
模板题
核心代码如下
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 |
|
进阶理解
\(\rm FWT\) 的变换本质实际上是构造 \(g\) 的过程。
设 \(\hat A_i = \sum_j g(i,j) A_j\),那么这个 \(n \times n\) 的 \(\lceil\)转移矩阵\(\rfloor\) 被称为位矩阵。
对于不同的卷积形式,我们需要构造不同的位矩阵。
特别注意由于我们需要逆变换,因此矩阵必须有逆。
形式化地,对于 \(\odot\) 卷积,即 \(C_i = \sum_{j \odot k = i} A_j B_k\),我们需要构造如下位矩阵:
写的不是很严谨,请读者自己手推一下。
对于上文的三类卷积,其位矩阵的构造显然为
\(\rm FWT\) 的线性性
设 \(\mathrm{FWT}(A)_i = \hat A_i = \sum_j g(i,j) A_j\),则有如下引理。
\(\mathrm{FWT}(A+B) = \mathrm{FWT}(A) + \mathrm{FWT}(B)\)
根据公式显然。
\(\mathrm{FWT}(cA) = c\mathrm{FWT}(A)\)
这个也是显然(
\(\mathrm{FWT}(A^{-1})_i = g(i,0) \mathrm{FWT}(A)_i^{-1}\)
FMT(Fast Möbius Transform) 学习笔记
小 Tips:在计算机语言中 \(\cap\) =
&
/and
, \(\cup\) =|
/or
定义
定义长度为 \(2^n\) 的序列的 and
卷积 \(A = B * C\) 为 \(A_i=\sum_{j \cap k = i}{B_j \times C_k}\)
考虑快速计算
Zeta 变换
定义长度为 \(2^n\) 的序列的 Zeta 变换
为
即子集和
它具有一下性质:
相当于 FFT
中的点值表示法,用以加速卷积过程
快速变换
暴力求解 \(\hat A\) 最优时间复杂度为 \(3^n\),考虑加速
考虑 子集DP
,有一篇很好的博客1,这里大概讲解一下。
其实 \(\hat A\) 本质上是一个高维(\(n\) 维)前缀和
如果我们要求二维前缀和,显然可以通过一下代码实现:
1 2 3 4 5 6 7 8 |
|
我们发现 1st
完成后 a[i][j]
表示的是 \(\sum_{k \le i} a_{k,j}\),即 \((i,j)\) 上方的元素和
那么 2nd
便是将 \((i,j)\) 左边操作后的 \(\sum_{k \le j} a_{i,k}\) 累加到 a[i][j]
中,即完成二维前缀和
下面拓展到三维前缀和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
类似地,1st,2nd
中处理除了第 k
平面中的二维前缀和,3rd
在立体空间中把他们加起来,成为三维前缀和
回到 Zeta 变换
:
如果我们用二进制 \((x_{n-1}x_{n-2}x_{n-3}\ ...\ x_2x_1x_0)_2\) 表示集合 \(i\),二进制 \((y_{n-1}y_{n-2}y_{n-3}\ ...\ y_2y_1y_0)_2\) 表示集合 \(j\)
那么 \(\hat A_i\) 可以被视为 \(n\) 维前缀和,即 $$ \hat A_i = \sum_{y_{n-1}\le x_{n-1},y_{n-2}\le x_{n-2}, ..., y_1\le x_1,y_0\le x_0} A_j $$
当然,下标都为 0/1
因此,我们有如下 Code:
1 2 3 4 |
|
以上 Code 的 naive 形式为:
1 2 3 4 |
|
不要质疑我代码编译不通过
因此,我们用 \(O(n2^n)\) 的时间复杂度解决了 Zeta 变换
逆变换
warning:此处不是
莫比乌斯反演
!
众所周知,前缀和的逆运算即为差分
我们发现,只需要先将最后一维差分,即可将序列处理为 \(n-1\) 维前缀和
即
1 2 3 4 |
|
但实际上因为前缀和都是无序的,因此我们直接正着做就可以啦
只需要把正变换的 +=
改为 -=
就可以了
扩展
有时候,题目给的不是 \(\cap\) ,而是 \(\cup\) 怎么办?
那我们重定义 Zeta 变换
为:
$$
\hat A_i = \sum_{i \supseteq j} A_j
$$
再推一下式子:
$$
\hat B_i \times \hat C_i
=\sum_{i\supseteq j} B_j \times \sum_{i\supseteq k} C_k
=\sum_{i\supseteq j,i\supseteq k} B_j \times C_k
=\sum_{i\supseteq p} \sum_{j \cup k=p} B_j \times C_k
=\sum_{i\supseteq p} A_p
=\hat A_i
$$
变换即超集和,高维后缀和
求法也很简单,只用将源代码中的 if(j&(1<<i))
该为 if(!(j&(1<<i)))
即可
是 \(\oplus\) 怎么办?
快去学 FWT 吧
例题
再送你一个并/交集的小口诀:下并或,上交与