学习笔记———NTT
基本原理和FFT是一样的,那么我们参考FFT,他是把单位根代入了进去,但是这样就会产生精度问题,那么NTT如何避免这个问题呢??
我们可以用原根来代替单位根
什么是原根呢??
其实我也不知道,但是给出一个结论:一个质数P的原根为\(g\),那么\(g^1,g^2\dots g^{p-1}\)他们模P后的结果各不相同
为什么可代替呢??
考虑在FFT中我们应用了单位根哪几个性子:
-
对于任意的\(1\leq w \leq n\),\(W_n^k\)都各不相同
-
\(W_n^k = W_{2n}^{2k}\)
-
\(W_{n}^{k+\frac{n}{2}} = -W_{n}^{k}\)
根据之前给出的结论,可以证明性质1成立,然后通过一些平方运算也可以证明剩下两条性质成立,那么我们就证明了他是可替代的
剩下的就和FFT一样了,代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define ll long long
#define B cout<<"Breakpoint"<<endl;
#define O(x) cout<<#x<<" "<<x<<endl;
#define o(x) cout<<#x<<" "<<x<<" ";
using namespace std;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
return x*a;
}
const int maxn = 3e6+10,G = 3,Gi = 332748118,P = 998244353;
int dp[maxn];
ll qpow(ll a,ll k){
ll res = 1;a = a % P;
while (k){
if (k&1) res = res*a % P;
a = a*a % P;
k >>= 1;
}
return res % P;
}
int n,m,len,num;
inline void NTT(ll *a,int op){
for (int i = 0;i < len;i++){
if (i < dp[i]) swap(a[i],a[dp[i]]);
}
for (int l = 1;l < len;l <<= 1){
ll wn = qpow(op == 1 ? G : Gi,(P-1)/(l << 1));
for (int i = 0;i < len;i += (l <<1)){
ll w0 = 1;
for (int j = 0;j < l;j++,w0 = w0*wn % P){
int x = a[i+j],y = w0*a[i+j+l] % P;
a[i+j] = (x+y) % P,a[i+j+l] = (x-y + P) % P;
}
}
}
}
ll a[maxn],b[maxn];
int main(){
n = read(),m = read();
for (int i = 0;i <= n;i++) a[i] = read();
for (int i = 0;i <= m;i++) b[i] = read();
len = 1,num = 0;
while (len <= n+m) len <<= 1,num++;
for (int i = 0;i < len;i++) dp[i] = (dp[i >> 1] >> 1) | ((i & 1) << (num-1));
NTT(a,1),NTT(b,1);
for (int i = 0;i < len;i++) a[i] = a[i]*b[i] % P;
NTT(a,-1);
ll inv = qpow(len,P-2);
for (int i = 0;i <= n+m;i++) printf("%d ",a[i]*inv % P);
return 0;
}