NTT(快速数论变换)和多项式求逆学习小记

NTT类似于FFT,只是把单位复数根改成了具有特殊性质的数,当然是在mo意义下的。

假设是mo一个数p。

p一定要是一个质数,并且p-1 = 2^x*y,2^x次方还要比较大。

原根:

对于一个质数p,它一定有原根g。

满足g^x(mod p)意义下互不相同(0<=x < < <script type="math/tex" id="MathJax-Element-1"><</script>p-1)

对于原根的判定,可以判断是否存在一个g^x=1(mod p)(1<=x < < <script type="math/tex" id="MathJax-Element-2"><</script>p-1),那么g就不是原根。

原根只能暴力找,但是由于原根都比较小,所以可以很快找到。

像1004535809、998244353的原根是3。

w的取用:

原来的 win w n i 对应 gi(mo1)n g i ∗ ( m o − 1 ) n

为什么呢?

对于单位复数根有两个核心要求(满足它们就行了):

1.互不相同
2. winwjn=w(i+j) mod nn w n i ∗ w n j = w n ( i + j )   m o d   n

因为g是原根,所以第一个性质成立。

对于第二个性质,显然有:
gi(mo1)ngj(mo1)n g i ∗ ( m o − 1 ) n ∗ g j ∗ ( m o − 1 ) n
=(gi)(mo1)n(gj)(mo1)n = ( g i ) ( m o − 1 ) n ∗ ( g j ) ( m o − 1 ) n
=(gi+j)(mo1)n = ( g i + j ) ( m o − 1 ) n

又有:
(gn)(mo1)n ( g n ) ( m o − 1 ) n
=gmo1 = g m o − 1
=1 = 1

所以:
=(gi+j)(mo1)n = ( g i + j ) ( m o − 1 ) n
=(g(i+j) mod n)(mo1)n = ( g ( i + j )   m o d   n ) ( m o − 1 ) n

所以这些数是可以完全替代单位复数根了。

NTT大概就是这样。

3(多)模数NTT:

有些题目(51nod上的)非常恶意。

多项式每一项的系数可能特别大,比如说10^22。

这时候你FFT用long double 精度炸了, 直接NTT也不行。

于是可以取3个NTT模数,最后中国剩余定理搞一下就行了。

多项式求逆:

对于一个多项式 A A .

B,满足 AB=1(mod xn) A ∗ B = 1 ( m o d   x n )

这个东西可以弄到大数除法里去,可惜只能是整除,有些题也需要它。

思路在于分治。

假设已经求出了 B B ′ ,满足 AB=1(mod xn/2) A ∗ B ′ = 1 ( m o d   x n / 2 )

显然有:
A(BB)=0(mod xn/2) A ∗ ( B − B ′ ) = 0 ( m o d   x n / 2 )
BB=0(mod xn/2) B − B ′ = 0 ( m o d   x n / 2 )
两边同时平方,模数也平方,得:
B22BB+B2=0(mod xn) B 2 − 2 B B ′ + B ′ 2 = 0 ( m o d   x n )
两边同时乘A,得:
B2B+AB2=0 B − 2 B ′ + A B ′ 2 = 0
B=B(2AB) B = B ′ ( 2 − A B ′ )

乘法用FFT加速。

复杂度是 T(n)=n log n+T(n/2)n log n T ( n ) = n   l o g   n + T ( n / 2 ) ≈ n   l o g   n

代码:

NTT:

void dft(ll *a, int n) {
    ff(i, 0, n)  {
        int p = i, q = 0;
        ff(j, 0, tp) q = q * 2 + p % 2, p /= 2;
        if(q > i) swap(a[q], a[i]);
    }
    for(int m = 2; m <= n; m *= 2) {
        int h = m / 2;
        ff(i, 0, h) {
            int W = w[i * (n / m)];
            for(int j = i; j < n; j += m) {
                int k = j + h;
                ll u = a[j], v = a[k] * W % mo;
                a[j] = (u + v) % mo; a[k] = (u - v + mo) % mo;
            }
        }
    }
}
void fft(ll *a, ll *b, int n) {
    ll rev = ksm(3, (mo - 1) / n);
    w[0] = 1; fo(i, 1, n) w[i] = w[i - 1] * rev % mo;
    dft(a, n); dft(b, n);
    ff(i, 0, n) a[i] = a[i] * b[i] % mo;
    fo(i, 0, n / 2) swap(w[i], w[n - i]);
    dft(a, n); ll ni = ksm(n, mo - 2);
    ff(i, 0, n) a[i] = a[i] * ni % mo;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值