快速数论变换NTT

前言

如果你没有看过快速傅里叶变换FFT,那么建议先去了解一下再来看这篇文章。

首先恭喜你,背出了FFT模板学会了FFT。而我们发现,由于FFT所用的大量实数操作,有些时候无法满足精度需求。或者说,遇到模意义下的操作,FFT也无法满足需求。

所以就有了NTT。

原根

FFT能执行的原因就是巧妙地选择了单位复根。那么我们需要在模意义下找到单位复根的替代品。

我们首先看一看我们需要具有什么性质的数:

1、$\omega_n^n=1$。

2、$\omega_n^0,\omega_n^1,…,\omega_n^{n-1}$互不相同。

3、$\omega_{kn}^{ki}=\omega_n^i$。

4、$\omega_n^k=-\omega_n^{k+\frac{n}{2}}$。

5、$\sum_{i=0}^{n-1}\omega_n^{ti}={\Large\{}\begin{matrix}0&t\neq0\\n & t=0\end{matrix}$。

然而本蒟蒻太菜了,只能到处找资料。

下面的两项定义及原根的求法参考https://www.cnblogs.com/cytus/p/9296661.html

首先我们定义阶:

若$m>1$且$gcd(a,m)=1$,那么使得$a^r\equiv1(mod\,\,m)$的最小正整数$r$称为$a$对模$m$的阶,记做$\delta_m(a)$。

然后定义原根:

设$m$为正整数,$a$为整数,如果$\delta_m(a)=\phi(m)$,那么称$a$为模$m$的一个原根。

原根的求法:

设$\phi(m)=p_1^{e_1}p_2^{e_2}…p_n^{e_n}​$,若$g^{\frac{\phi(m)}{p_i}} \mod m \neq 1​$,那么$g​$是$m​$的一个原根。

常见的模数及其原根:http://blog.miskcoo.com/2014/07/fft-prime-table

原根性质的证明可以参考https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html

参考代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;

const LL Mod = 998244353;
const LL Maxn = 400010;
LL N, M, TotalLen, L;
LL A[ Maxn ], B[ Maxn ];
LL Index[ Maxn ], Omega[ Maxn ];
char Ch[ Maxn ];

void Exgcd( LL a, LL b, LL &x, LL &y ) {
    if( b == 0 ) {
        x = 1; y = 0; return;
    }
    Exgcd( b, a % b, y, x );
    y -= a / b * x;
    return;
}

LL Inv( LL a ) {
    LL x, y;
    Exgcd( a, Mod, x, y );
    if( x < 0 ) x += Mod;
    return x;
}

LL FastPow( LL x, LL y ) {
    if( y == 0 ) return 1;
    LL t = FastPow( x, y / 2 );
    t = t * t % Mod;
    if( y & 1 ) t = t * x % Mod;
    return t;
}

void Read() {
    scanf( "%s", Ch );
    N = strlen( Ch );
    for( LL i = 0; i < N; ++i ) A[ i ] = Ch[ N - 1 - i ] - '0';
    scanf( "%s", Ch );
    M = strlen( Ch );
    for( LL i = 0; i < M; ++i ) B[ i ] = Ch[ M - 1 - i ] - '0';
    return;
}

void Init() {
    TotalLen = N + M;
    for( L = 1; L < TotalLen; L <<= 1 );
    for( LL i = 0; i < L; ++i ) 
        Index[ i ] = ( Index[ i >> 1 ] >> 1 ) | ( ( i & 1 ) * ( L >> 1 ) );
    Omega[ 0 ] = 1; Omega[ 1 ] = FastPow( 3, ( Mod - 1 ) / L );
    for( LL i = 2; i < L; ++i ) 
        Omega[ i ] = Omega[ i - 1 ] * Omega[ 1 ] % Mod;
    return;
}

void NTT( LL *A ) {
    for( LL i = 0; i < L; ++i ) 
        if( Index[ i ] > i )
            swap( A[ i ], A[ Index[ i ] ] );
    for( LL HalfLen = 1; HalfLen < L; HalfLen <<= 1 ) 
        for( LL i = 0; i < L; i += HalfLen << 1 )
            for( LL j = 0; j < HalfLen; ++j ) {
                LL T = Omega[ L / 2 / HalfLen * j ] * A[ i + j + HalfLen ] % Mod;    
                LL t = A[ i + j ];
                A[ i + j ] = ( t + T ) % Mod;
                A[ i + j + HalfLen ] = ( t - T + Mod ) % Mod;
            }
    return;
}

void Transform() {
    for( LL i = 0; i < L; ++i ) A[ i ] = ( A[ i ] * B[ i ] ) % Mod;
    Omega[ 1 ] = Inv( Omega[ 1 ] );
    for( LL i = 2; i < L; ++i )
        Omega[ i ] = Omega[ i - 1 ] * Omega[ 1 ] % Mod;
    return;
}

void Print() {
    LL INV = Inv( L );
    for( LL i = 0; i < L; ++i ) 
        A[ i ] = A[ i ] * INV % Mod;
    for( LL i = 0; i < L; ++i ) {
        A[ i + 1 ] += A[ i ] / 10;
        A[ i ] %= 10;
    }
    while( L && !A[ L ] ) --L;
    for( LL i = L; i >= 0; --i ) printf( "%lld", A[ i ] );
    printf( "\n" );
    return;
}

int main() {
    Read();
    Init();
    NTT( A ); NTT( B );
    Transform();
    NTT( A );
    Print();
    return 0;
}

  Reprint please specify: chy's blog 快速数论变换NTT

 Previous
多项式求逆 多项式求逆
概述多项式求逆是多项式除法的基础。通过快速傅里叶变换和倍增,我们可以在$O(n \log n)$的复杂度内完成。 概念定义$\deg A$表示多项式$A$的最高次数。 对于两个多项式$A$和$B$,有唯一的$A=QB+R$。其中$\deg
2019-04-17
Next 
多项式学习 多项式学习
快速傅里叶变换FFT快速数论变换NTT多项式求逆多项式除法
2019-04-16
  TOC