任意模数NTT(MTT)
任意模数NTT(MTT)
问题的简单描述为,求解两个值域为 \(\leq 10^9\) 的多项式卷积对于 \(P\leq 10^9\) 取模的结果。
问题不能直接用NTT/FFT求解,因为均超过了值域范围(double值域承受不了)。
Solution1:3模数NTT
取几个互质的模数分别做一次,然后用中国剩余定理合并。
由于值域大,通常需要多次 NTT,且中国剩余定理合并常数也不小。
实际代码实现也复杂,因此笔者认为不可取。
Solution2:拆系数FFT
设 \(f(x)=\sum a_ix^i\)。
核心:将系数 \(a_i\) 分解成 \(a_i=A_i\cdot S+C_i,b_i=B_i\cdot S+D_i\)。
(其中 \(S\ge \sqrt{P}\) 是一个常数, \(0 \leq A_i,B_i,C_i,D_i<S\) )。
目的是转化后使系数值域变小,double 精度可以承受。
则最后的答案转化为求解 \(A_iB_jS^2+(C_iB_j+A_iD_j)S+C_iD_j\)。
即求解 \(A_iB_j,C_iB_j,A_iD_j,C_iD_j\) ,此时值域已经大大缩小。
如果直接求解,可以看出要求解4次卷积,需要进行 \(12\) 次FFT,不可接受。
利用复数的一些性质,有些东西我们可以一起算。
构造:
\(f(x)=\sum (A_i,C_i)x^i\),
\(g(x)=\sum(B_i,D_i)x^i\),
\(f(x)g(x)=\sum \sum (A_iB_j-C_iD_j, A_iD_j+C_iB_j)x^{i+j}\)。
此时已经得到大部分值了,再构造:
\(h(x)=\sum B_ix^i\),
\(f(x)h(x)=\sum \sum (A_iB_j,C_iB_j)x^{i+j}\)。
取一部分即可。
最终一共有 5 次 FFT。
Tips:
1.这里的负数取整一定要注意,因为 C++ 默认是向 0 取整,而不是向下取整。
2.实际运行表明,这样写用 double 很难保证精度,应该要用 long double
附:
4次FFT做MTT,但是具体证明比较反人类,而代码非常好看且好写,所以建议直接背板子
Tips: 只要使用了上面提到的最适合FFT的板子,就可以用double,甚至可以开O2
1 | namespace MTT{ |