任意模数多项式乘法-MTT
拆系数FFT
在之前,我们用 原根
代替 单位根
,实现了比 FFT
更快的 NTT
但 NTT
的限制比较大:模数能表示为 \(a\times 2^k+1\),通常这个 \(k\) 要求大于 \(16\)
如果我们换了一个不太友好的模数(如 \(10^9+7\), ),似乎 19190817
NTT
就发挥不了了
有一种跑 \(9\) 次 NTT
的实现方法,但常数大 + \(10^{26}\) 的值域 long long
也存不下,不太好实现
其实我们这里的瓶颈就是精度的问题,如果我们可以将值域缩减,精度就可以提高,通过跑 FFT
再取模也是可以的
一种做法就是将系数拆成 \(a_1\times 2^{15}+b_1\) 的形式
那么卷积的时候就是 \((a_1\times 2^{15}+b_1)(a_2\times 2^{15}+b_2)=a_1a_2\times 2^{30}+(a_1b_2+a_2b_1)\times 2^{15}+b_1b_2\)
我们可以考虑将 \(a_1,b_1,a_2,b_2\) 都用多项式表示出来
但这样我们需要 \(8\) 次的 FFT
参考 三次变两次
的做法,我们考虑构造复多项式 \(P=A_1+B_1i\), \(Q=A_2+B_2i\)
那么 \(P*Q=A_1A_2-B_1B_2+(A_1B_2+A_2B_1)i\),这样就得到 \(2^{15}\) 的那一项了
再构造 \(P\) 的共轭多项式 \(P'=A_1-B_1i\),有 \(P'Q=A_1A_2+B_1B_2-(A_1B_2-A_2B_1)i\)
\(PQ+P'Q\) 可得 \(2A_1A_2\),\(P'Q-PQ\) 可得 \(2B_1B_2\)
这样就可以跑出来了,多项式乘积的值域就是 \(10^{14}\) 的了
忠告:
-
std::cos
和std::sin
的精度比cos
和sin
的要高(慢点无所谓,但得能卡过去啊) -
在点值相乘的时候就除以 \(len\),别
IDFT
后再除(为了降低值域,把精度卡上去) -
强制转换时要记得
floor
(四舍五入),并马上取模(防止爆long long
)
(卡精度卡到自闭了,还不如去学 3模数NTT
)
练习题:
(没啥好说的,就是MTT+多项式逆元
而已)