任意模数多项式乘法-大模数快速数论变换
本文作者为 JustinRochester。
任意模数多项式乘法
在部分题目中,我们的多项式运算结果并不是对多项式模数(如 \(998244353\))取模,而是对一些指定的(甚至是非质数的模数)取模。
为了解决这个问题,我们引入任意模数的多项式乘法。
首先需要明确的是:任意模数意义下的多项式乘法,是转化成我们先前讲过的多项式乘法的。
为此,我们需要清楚地知道,在后续的提到方法中,当且仅当两个模 \(M\) 意义下的多项式经过模 \(M\) 乘法,能得到一个模 \(M\) 意义下的多项式。
也就是说,假设我们有三个模 \(M\) 意义下的多项式 \(A(x), B(x), C(x)\) ,我们计算 \(A(x), B(x)\) 的模 \(M\) 意义下的乘法步骤是:
- 将 \(A(x), B(x)\) 的模 \(M\) 乘法转化为普通乘法的问题;
- 计算普通乘法问题;
- 将普通乘法结果还原为原多项式。
而计算三者模 \(M\) 乘积的步骤是:
- 计算其中两个的模 \(M\) 乘积;
- 计算乘积结果和剩下那个多项式的模 \(M\) 乘积。
一个常见的错误做法是:
- 将 \(A(x), B(x), C(x)\) 的模 \(M\) 乘法转化为普通乘法的问题;
- 直接计算三者普通乘法问题;
- 将普通乘法结果还原为原多项式。
错误的原因就在于它并不是将两个模 \(M\) 的多项式乘法,这样并不能保证得出正确的结果。
当然,后续的分析可以指导你,这样的计算方法怎么得出正确的结果;但不是本节课的重点,也不常用于模板。
大模数快速数论变换
我们假设有一个多项式 \(\displaystyle F(x)=\sum_{i=0}^{n-1} f_ix^i\) ,其中每个系数都是原给定多项式在模 \(M\) 意义下的结果。因此,有 \(f_i<M\) 。
我们不妨假设计算 \(F^2(x)\) 的结果。则 \(\displaystyle F^2(x)=\sum_{i=0}^{2n-2} x^i\sum_{p+q=i}f_pf_q\) 。
因此,计算后的多项式系数,范围为 \(\displaystyle \sum_{p+q=i}f_pf_q\leq \sum_{p+q=i}M\cdot M\leq \sum_{p+q=n-1}M^2\leq n\cdot M^2\) 。
因此,一个可以的方法是我们挑一个比较大的多项式模数 \(P\geq n\cdot M^2\) 。
这样一来,我们直接按原多项式的方法,在模多项式模数 \(P\) 的意义下进行计算,得到结果后,最后再将各系数模 \(M\) 。
当模数的范围较小,在 \(10^6\) 规模时;考虑到多项式长度一般有 \(n\leq 10^6\) ,故系数 \(n\cdot M^2\leq 10^{18}\) 。因此,一个比较棒的模数是 \(P=29\cdot 2^{57}+1=4179340454199820289\approx 4\cdot 10^{18}\) ,它的原根也为 \(3\)。
当然,这种方法需要计算模 \(P\) 意义下的乘法结果时,可能需要用到 __int128_t
的乘法;或者通过该代码进行:
inline ull mul(ull a, ull b, ull p=P) { return (a*b-(ull)((long double)a/p*b)*p+p)%p; }
当然,涉及到多个多项式直接相乘,最终再还原回模 \(M\) 域时,不妨假设有 \(m\) 个多项式进行乘法,则系数有:
\(\displaystyle \sum_{i_1+i_2+\cdots +i_m=i}f_{1, i_1}f_{2, i_2}f_{3, i_3}\cdots f_{m, i_m}\leq \sum_{i_1+i_2+\cdots +i_m=i} M^m\leq M^m\sum_{i_1+i_2+\cdots +i_m=n}1\leq M^m\dbinom {n+m-1} {m-1}=O(M^m\cdot n^{m-1})\)
同理,只要选择模数 \(P\geq M^m\cdot n^{m-1}\) ,就可以实现多次乘法,再还原。
然而,当这个模数 \(P\) 过大时,若不能在 __int128_t
的范围内实现,还需要手动实现高精度运算。高精度的乘法又可能需要用到 FFT 加速,有些得不偿失了。
因此,比较常见的就是让模数能够在 __int128_t
的范围内,保证方便取模。
然而,我们常见的模数是 int
范围内,在 \(2\times 10^9\) 左右;而多项式长度同样是 \(n\leq 10^6\) ,两个多项式的乘法一般也会达到 \(4\times 10^{24}\) 的范围,这种方法显然没有办法实现。
我们下两讲会分别提到多模数 NTT 和 MTT 来解决这个问题。