Loading

NTT 学习笔记

引入

\(\tt NTT\)\(\tt FFT\) 有什么不一样呢?
就是 \(\tt NTT\) 是可以用来取模的,而且没有复数带来的精度误差。
最最重要的是据说 \(\tt NTT\) 常数小的很,可以在这一方面吊打 \(\tt FFT\)

至于对于不用取模的多项式乘法怎么做,可以给他附一个非常大的模数。
但是如果遇到有非整数的系数的多项式还是只能看 \(\tt FFT\) 了。

正文

\(\tt NTT\) 的前置知识比 \(\tt FFT\) 的要简单的多。

原根

对于 \(p\)\(g\) 两个整数,若

\[g^i\ \text{mod} \ g\ (1\leq i\leq p -1) \]

这个式子的值都不相同,那么 \(g\)\(p\) 的原根。
通常 \(\tt NTT\) 的题目会给 \(998244353\) , \(1004535809\) , \(469762049\) 这几个模数。
他们的原根都是 \(3\)

但是这个我嫌不够过瘾,所以就放一个在网上找到的牛逼原根表。

//(g 是mod(r*2^k+1)的原根)

素数  r  k  g
3   1   1   2
5   1   2   2
17  1   4   3
97  3   5   5
193 3   6   5
257 1   8   3
7681    15  9   17
12289   3   12  11
40961   5   13  3
65537   1   16  3
786433  3   18  10
5767169 11  19  3
7340033 7   20  3
23068673    11  21  3
104857601   25  22  3
167772161   5   25  3
469762049   7   26  3
1004535809  479 21  3
2013265921  15  27  31
2281701377  17  27  3
3221225473  3   30  5
75161927681 35  31  3
77309411329 9   33  7
206158430209    3   36  22
2061584302081   15  37  7
2748779069441   5   39  3
6597069766657   3   41  5
39582418599937  9   42  5
79164837199873  9   43  5
263882790666241 15  44  7
1231453023109121    35  45  3
1337006139375617    19  46  3
3799912185593857    27  47  5
4222124650659841    15  48  19
7881299347898369    7   50  6
31525197391593473   7   52  3
180143985094819841  5   55  6
1945555039024054273 27  56  5
4179340454199820289 29  57  3

转载 这篇博客的

\(\tt NTT\)

其实 \(\tt NTT\) 就是把原根替换 \(\tt FFT\) 中的单位根。

\(\tt FTT\) 之所以牛逼,是因为 \(\omega\) 有一些非常奇妙的性质。
然后神仙们发现其实原根也有非常牛逼的性质。

当此时的合并区间的长度为 \(L=2\times l\)
单位根是

\[\cos\frac{2\pi}{L}+i\times\sin\frac{2\pi}{L}=\cos\frac{\pi}{l}+i\times\sin\frac{\pi}{l} \]

而原根是:

\[g^{\frac{p-1}{L}}=g^{\frac{p-1}{2\times l}} \]

其他和 \(\tt FFT\) 就基本没有什么区别了。

inline void NTT(int a[], int len, int inv) {
  int bit = 0;
  while ((1 << bit) < len) ++bit;
  for (int i = 0; i <= len - 1; i++) {
    rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    if (i < rev[i]) std::swap(a[i], a[rev[i]]);
  }
  for (int mid = 1; mid < len; mid *= 2) {
    int tmp = power(g, (mod - 1) / (mid * 2)); 
    if (inv == -1) tmp = power(tmp, mod - 2); 
    for (int i = 0; i < len; i += mid * 2) {
      int omega = 1;
      for (ll j = 0; j < mid; ++j, omega = omega * tmp % mod) {
        int x = a[i + j];
        int y = omega * a[i + j + mid] % mod;
        a[i + j] = (x + y) % mod;
        a[i + j + mid] = (x - y + mod) % mod;
      }
    }
  }
}

这个代码也不是我原创的,在网上随便贺了一篇代码。
调用的话就和 \(\tt FFT\) 基本上一模一样。

posted @ 2022-06-12 22:57  Aonynation  阅读(43)  评论(0编辑  收藏  举报