NTT

NTT

1.概念

数论变换由于快速傅里叶变换的提出,大大减少了计算运算次数在有循环卷积特性的条件下,快速数论变换是具有比快速傅里叶更快的快速变换算法。

2.生成子群

子群 : 群 \((S,⊕) , (S',⊕)\) , 满足 \(S'⊂S\) , 则 \((S',⊕)\)\((S,⊕)\) 的子群

3.原根

\(m\) 是正整数,\(a\) 是整数,若 \(a\)\(m\) 的阶等于 \(φ(m)\),则称 \(a\) 为模 \(m\) 的一个原根。(其中 \(φ(m)\) 表示 \(m\) 的欧拉函数)

假设一个数 \(g\)\(P\) 的原根,那么 \(g^i\) \(mod\) \(P\) 的结果两两不同,且有 \(1<g<P,0<i<P\) ,归根到底就是 \(g^{P-1}\) \(=\) \(1\) \((mod\) \(P)\) 当且仅当指数为 \(P-1\) 的时候成立.(这里 \(P\) 是素数)。

简单来说,\(g^i\) \(mod\) \(p ≠ g^j\) \(mod\) $p $( \(p\) 为素数),其中 \(i≠j\)\(i\), \(j\) 介于 \(1\)\((p-1)\) 之间,则 \(g\)\(p\) 的原根。

4.NTT 快速数论变化

FFT 利用单位根 \(ω\) 的性质实现分治优化多项式乘法,原根也有这样的性质,

NTT 即用原根代替 \(ω\) 的 FFT,这样可以不用复数,且实现复数取模运算

对于质数 \(p=qn+1,(n=2^m)\) , 原根 \(g\) 满足 \(g^{qn}≡1(mod\) \(p)\)

\(g^n=g^p(mod\) \(q)\) , 看作 \(ω_n\) 的等价。选择二者相似的性质

\(g_n^{n}≡1(mod\) \(p)\) , \(g_n^{\frac{n}{2}}≡-1(mod\) \(p)\)

即将 \(g^\frac{p-1}{len}\) \((mod\) \(p)\) ,看作 \(ω_n=e^{-\frac{-2πi}{n}}\) 的等价 , 即 \(e^{-\frac{-2πi}{n}}\) \(≡\) \(g^\frac{p-1}{len}\) \((mod\) \(p)\)

具体实现 : 当合并区间长度为 \(len=2mid\) 时,

  • 单位根 \(ω_n'=cos\frac{2π}{len}+i\) \(sin\frac{2π}{len}=cos\frac{π}{mid}+i\) \(sin\frac{π}{mid}\)
  • 原根 : \(g^\frac{p-1}{len}=g^\frac{p-1}{2mid}\)

6.代码 P3803 【模板】多项式乘法(FFT)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 5e6 + 5;
const int mod = 998244353;

int n, m, A[N], B[N], lim, r[N];

int qpow(int a, int b) {
    int res = 1 % mod;

    for (; b; b >>= 1) {
        if (b & 1)
            res = 1ll * res * a % mod;

        a = 1ll * a * a % mod;
    }

    return res;
}

void init(int n) {
    int l = -1;

    for (lim = 1; lim < n; lim <<= 1) {
        l++;
    }

    for (rint i = 1; i < lim; i++) {
        r[i] = (r[i >> 1] >> 1) | ((i & 1) << l);
    }

    return ;
}

void NTT(int *x, int inv) {
    int l = 0, res = 0, g = 0; //

    for (rint i = 0; i < lim; ++i) {
        if (i < r[i]) {
            swap(x[i], x[r[i]]);
        }
    }

    for (rint i = 2; i <= lim; i <<= 1) {
        l = i >> 1, res = qpow(inv == 1 ? 3 : 332748118, (mod - 1) / i);

        for (rint *p = x; p != x + lim; p += i) {
            g = 1;

            for (rint j = 0; j < l; ++j) {
                int t = 1ll * g * p[j + l] % mod;
                p[j + l] = (p[j] - t < 0 ? p[j] - t + mod : p[j] - t), p[j] = (p[j] + t > mod ? p[j] + t - mod : p[j] + t);
                g = 1ll * g * res % mod;
            }
        }
    }

    if (inv == -1) {
        int invl = 1ll * qpow(lim, mod - 2);

        for (rint i = 0; i <= lim; ++i) {
            x[i] = 1ll * x[i] * invl % mod;
        }
    }

    return ;
}

signed main() {
    cin >> n >> m;

    init(n + m + 1);

    for (rint i = 0; i <= n; i++) {
        scanf("%d", &A[i]);
    }

    for (rint i = 0; i <= m; i++) {
        scanf("%d", &B[i]);
    }

    NTT(A, 1);
    NTT(B, 1);

    for (rint i = 0; i < lim; i++) {
        A[i] = 1ll * A[i] * B[i] % mod;
    }

    NTT(A, -1);

    for (rint i = 0; i < n + m + 1; i++) {
        printf("%d ", A[i]);
    }

    return 0;
}
posted @ 2022-07-27 10:46  PassName  阅读(291)  评论(0编辑  收藏  举报