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;
}