任意模数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{ |