这是一篇对于新线性递推论文的解读,你可以在这里找到原文。
一个众所周知的线性递推计算方法是考虑的通过快速幂计算多项式取模 x N m o d Q ( x ) x^N \bmod Q(x) xNmodQ(x),这被称为 Fiduccia 算法(1985)。设做多项式乘法的时间为 M ( n ) \mathsf M(n) M(n),在一般环上的计算需要约 ( 3 log N + O ( 1 ) ) M ( d ) \mathsf (3\log N + O(1))\mathsf M(d) (3logN+O(1))M(d) 的运算量,即使在可以做 FFT 的运算下也只能做到 ( 2 log N + O ( 1 ) ) M ( d ) \mathsf (2\log N + O(1))\mathsf M(d) (2logN+O(1))M(d) 的运算量。
而新的算法可以做到一般环上 ( 2 log N + O ( 1 ) ) M ( d ) (2\log N + O(1))\mathsf M(d) (2logN+O(1))M(d) 的运算量,和在可以做 FFT 的运算下做到 ( 2 3 log N + O ( 1 ) ) M ( d ) (\frac 23 \log N + O(1))\mathsf M(d) (32logN+O(1))M(d) 的运算量。也就是说在我们通常有 NTT 模数的情况下,同样的 FFT 实现可以让我们的常数大约是原先方法的 1 3 \frac {\mathbf 1}{\mathbf3} 31。
新线性递推算法的主要思想是考虑将线性递推转化为生成函数的形式 P ( x ) Q ( x ) \frac{P(x)}{Q(x)} Q(x)P(x)。注意我们容易在 M ( n ) \mathsf M(n) M(n) 的时间内通过数列的前 d d d 项得到 P ( x ) P(x) P(x),只需计算 [ ( ∑ i < d f i x i ) Q ( x ) ] m o d x d \left[(\sum_{i<d} f_i x^i) Q(x)\right] \bmod x^d [(∑i<dfixi)Q(x)]modxd。接下来我们考虑这样一件事:
P ( x ) Q ( x ) = P ( x ) Q ( − x ) Q ( x ) Q ( − x ) \frac {P(x)}{Q(x)} = \frac{P(x)Q(-x)}{Q(x)Q(-x)} Q(x)P(x)=Q(x)Q(−x)P(x)Q(−x)
这发生了什么呢?我们注意记 V ( x ) = Q ( x ) Q ( − x ) V(x)=Q(x)Q(-x) V(x)=Q(x)Q(−x),可以发现 V ( x ) = V ( − x ) V(x)=V(-x) V(x)=V(−x),说明 V ( x ) V(x) V(x) 只有偶次项有值,因此我们就得到了分解
P ( x ) Q ( x ) = E ( x 2 ) U ( x 2 ) + x O ( x 2 ) U ( x 2 ) , U ( x 2 ) = Q ( x ) Q ( − x ) \frac{P(x)}{Q(x)} = \frac {E(x^2)}{U(x^2)} + x\frac {O(x^2)}{U(x^2)}, \quad U(x^2)=Q(x)Q(-x) Q(x)P(x)=U(x2)E(x2)+xU(x2)O(x2),U(x2)=Q(x)Q(−x)
因为这分别填满了二进制的 0 0 0 和 1 1 1 位,所以我们只需递归到一侧即可。
不难发现朴素实现就是上下都做多项式乘法,每一个二进制位约消耗 2 M ( d ) 2\mathsf M(d) 2M(d) 的时间。
接下来我们看看可以如何优化常数。我们记 DFT 的时间为 E ( 2 n ) \mathsf E(2n) E(2n),可得 M ( n ) = 3 E ( 2 n ) \mathsf M(n)=3\mathsf E(2n) M(n)=3E(2n)。
→ 4 3 M ( d ) \rightarrow \frac 43 \mathsf M(d) →34M(d)
我们注意当知道 Q ( x ) Q(x) Q(x) 的 DFT 时,可以直接得到 Q ( − x ) Q(-x) Q(−x) 的 DFT。记 A k A_k Ak 的 DFT 数组为 A ^ k \widehat A_k A k,读者不难自行验证 B k = ( − 1 ) k A k B_k=(-1)^kA_k Bk=(−1)kAk 的 DFT 数组对应有 B ^ k = A ^ k ⊕ n 2 \widehat B_k = \widehat A_{k \oplus \frac n2} B k=A k⊕2n。
因此我们减少了 1 3 \frac 13 31 的 FFT 运算量,变为 4 E ( 2 d ) = 4 3 M ( d ) 4\mathsf E(2d)=\frac 43 \mathsf M(d) 4E(2d)=34M(d)。
→ M ( d ) \rightarrow \mathsf M(d) →M(d)
注意到 Q ( x ) Q ( − x ) Q(x)Q(-x) Q(x)Q(−x) 最后只剩下偶数项,而 P ( x ) Q ( − x ) P(x)Q(-x) P(x)Q(−x) 我们要么只要奇数项要么只要偶数项,假设我们要提取 A k A_k Ak 的所有偶数项,那么设 B k = A k + ( − 1 ) k A k 2 B_k = \frac{A_k + (-1)^k A_k}{2} Bk=2Ak+(−1)kAk,可得 B ^ k = A ^ k + A ^ k ⊕ n 2 2 \widehat B_k = \frac{\widehat A_k + \widehat A_{k \oplus \frac n2}}2 B k=2A k+A k⊕2n,然后对前 n / 2 n/2 n/2 项做长为 n n n 的 IDFT 即可,奇数项是类似的。
因此我们将其中的两个 E ( 2 d ) \mathsf E(2d) E(2d) 换成了 E ( d ) \mathsf E(d) E(d),运算量变为 2 E ( 2 d ) + 2 E ( d ) = M ( d ) 2\mathsf E(2d) + 2\mathsf E(d) = \mathsf M(d) 2E(2d)+2E(d)=M(d)。
→ 2 3 M ( d ) \rightarrow \frac 23\mathsf M(d) →32M(d)
我们考虑不做上一步的 IDFT,始终以 DFT 的形式维护 P , Q P,Q P,Q。那么我们是否能够更小常数地从 A A A 的长为 n n n 的 DFT 推出长为 2 n 2n 2n 的 DFT 呢?
事实上我们可以在 2 E ( n ) 2\mathsf E(n) 2E(n) 内完成。只需注意到以下两件事:
A ^ 2 k ( 2 n ) = A ^ k A ^ 2 k + 1 ( 2 n ) = ∑ i ( ω 2 n i A i ) ω n i k \begin{aligned} \widehat A^{(2n)}_{2k} &= \widehat A_k\\ \widehat A^{(2n)}_{2k+1} &= \sum_i (\omega_{2n}^{i}A_i) \omega_n^{ik} \end{aligned} A 2k(2n)A 2k+1(2n)=A k=i∑(ω2niAi)ωnik
因此我们先 IDFT 得到 A A A,然后乘以 ω 2 n i \omega_{2n}^i ω2ni 再做 DFT 就可以了。
现在整个迭代流程只有对 P , Q P,Q P,Q 的 DFT 数组倍长用到了 FFT,运算量为 4 E ( d ) = 2 3 M ( d ) 4\mathsf E(d) = \frac 23 \mathsf M(d) 4E(d)=32M(d)。
你可能认为这是改了一个问题所以变容易了,其实并不,我们可以将原问题:计算 x N m o d r e v ( Q ) x^N \bmod rev(Q) xNmodrev(Q) 的时间也降低常数!
我们考虑计算 1 Q ( x ) \frac 1{Q(x)} Q(x)1 的第 N , N + 1 , … , N + d − 1 N, N+1,\dots, N+d-1 N,N+1,…,N+d−1 项,由 1 Q ( x ) = Q ( − x ) Q ( x ) Q ( − x ) \frac 1{Q(x)} = \frac {Q(-x)}{Q(x)Q(-x)} Q(x)1=Q(x)Q(−x)Q(−x) 可知,我们只需算出 1 Q ( x ) Q ( − x ) \frac 1{Q(x)Q(-x)} Q(x)Q(−x)1 的连续 2 d 2d 2d 项,也就是 U ( x 2 ) = Q ( x ) Q ( − x ) U(x^2)=Q(x)Q(-x) U(x2)=Q(x)Q(−x) 后 1 U ( x ) \frac 1{U(x)} U(x)1 的连续 d d d 项。
其中, Q → U Q\rightarrow U Q→U 这个过程每轮还是 1 3 M ( d ) \frac 13 \mathsf M(d) 31M(d) 的,这个过程已经算出了 Q ( − x ) Q(-x) Q(−x) 的 DFT,而我们要和 1 U ( x 2 ) \frac 1{U(x^2)} U(x2)1 相乘,注意到这里 2 d 2d 2d 长的 DFT 足够,因为后面的部分会溢出掉,所以这个 loop 的总共消耗就是恰好的 M ( d ) \mathsf M(d) M(d)。即总耗时是 ( log N + O ( 1 ) ) M ( d ) (\log N + O(1))\mathsf M(d) (logN+O(1))M(d)。
接下来注意到从第 N N N 项截取的 1 Q ( x ) \frac 1{Q(x)} Q(x)1 记为 P ( x ) P(x) P(x),那么 r e v ( P ( x ) Q ( x ) m o d x d ) rev(P(x)Q(x) \bmod x^d) rev(P(x)Q(x)modxd) 既得。
上面那一小段是原论文的见解。但是事实上我们之间将一开始的算法转置就是多项式取模了。