Fast Number Theoretical Transform (NTT) Summary

NTT

In FFT, we need to use complex numbers. Although complex numbers are amazing, they also have their own limitations - they need to be calculated in double type, and the precision is too low

So is there anything that can replace complex numbers and solve the precision problem?

This thing is called the original root

Primitive root

Definition of Primitive Root

Let \(m\) be a positive integer, \(a\) is an integer, if the order of \(a\) modulo \(m\) is equal to \(\phi(m)\) , then \(a\) is called A primitive root modulo \(m\)

Some knowledge of group theory is used in the definition, but it does not matter if it is not, it does not affect the subsequent learning

We define \(P\) as a prime number, \(g\) as the original root of \(P\)

Then throw out a very important theorem without proof

  • If \(P\) is a prime number, assuming a number \(g\) is the original root of \(P\) , then \(g^i \mod P (1<g<P,0<i<P)\ ) , the results are different

Don't ask me why, because I don't know either. .




Consider why the original root can replace the unit root for operation, (this part can be skipped)

The reason is simple because it has the same properties as the unit root

In the FFT, we use the four properties of the unit root, and the original root also satisfies these four properties

1 . Not the same for all \(\omega_n ^ t (0 \leq t \leq n - 1)\)

This can be obtained from the above theorem

2 . \(\omega_{2n} ^ {2k} = \omega_n ^ k\)

can be obtained by substitution

3 . \(\omega_n ^ { k + \frac{n}{2} } = -\omega_n ^ k\)

According to Fermat's little theorem and property 1, we can get

4 . $1 + \omega_n ^ k + (\omega_n ^ k) ^ 2 + \dots + (\omega_n ^ k) ^ {n - 1} = 0 $

From property 3 and the theorem of inverse Fourier transform in FFT, we can get

So we can finally come to a conclusion

\[\omega_n \equiv g^\frac{p-1}{n} \mod p\]

Then just replace all \(\omega_n\) in FFT.

\(p\) It is recommended to take \(998244353\) , its original root is \(3\) .

How to find the primitive root of any prime number?

It can be proved that the smallest \(r\) satisfying \(g^r \equiv 1(\mod p)\ ) must be a divisor of \(p-1\)

For prime numbers \(p\) , prime factorization \(p−1\) , if \(g^{\frac{p-1}{p_i}} \neq 1 \pmod p\) is constant, \(g \) is the original root of \(p\)

accomplish

NTT convolution code:

It is indeed much faster than FFT

#include<cstdio>
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++)
#define swap(x,y) x ^= y, y ^= x, x ^= y
#define LL long long 
const int MAXN = 3 * 1e6 + 10, P = 998244353, G = 3, Gi = 332748118; 
char buf[1<<21], *p1 = buf, *p2 = buf;
inline int read() { 
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int N, M, limit = 1, L, r[MAXN];
LL a[MAXN], b[MAXN];
inline LL fastpow(LL a, LL k) {
    LL base = 1;
    while(k) {
        if(k & 1) base = (base * a ) % P;
        a = (a * a) % P;
        k >>= 1;
    }
    return base % P;
}
inline void NTT(LL *A, int type) {
    for(int i = 0; i < limit; i++) 
        if(i < r[i]) swap(A[i], A[r[i]]);
    for(int mid = 1; mid < limit; mid <<= 1) {  
        LL Wn = fastpow( type == 1 ? G : Gi , (P - 1) / (mid << 1));
        for(int j = 0; j < limit; j += (mid << 1)) {
            LL w = 1;
            for(int k = 0; k < mid; k++, w = (w * Wn) % P) {
                 int x = A[j + k], y = w * A[j + k + mid] % P;
                 A[j + k] = (x + y) % P,
                 A[j + k + mid] = (x - y + P) % P;
            }
        }
    }
}
int main() {
    N = read(); M = read();
    for(int i = 0; i <= N; i++) a[i] = (read() + P) % P;
    for(int i = 0; i <= M; i++) b[i] = (read() + P) % P;
    while(limit <= N + M) limit <<= 1, L++;
    for(int i = 0; i < limit; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (L - 1));  
    NTT(a, 1);NTT(b, 1);    
    for(int i = 0; i < limit; i++) a[i] = (a[i] * b[i]) % P;
    NTT(a, -1); 
    LL inv = fastpow(limit, P - 2);
    for(int i = 0; i <= N + M; i++)
        printf("%d ", (a[i] * inv) % P);
    return 0;
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325128034&siteId=291194637