​http://www.elijahqi.win/2018/03/17/uoj34ntt/​​​
这是一道模板题。

给你两个多项式,请输出乘起来后的多项式。

输入格式
第一行两个整数 nn 和 mm,分别表示两个多项式的次数。

第二行 n+1n+1 个整数,表示第一个多项式的 00 到 nn 次项系数。

第三行 m+1m+1 个整数,表示第二个多项式的 00 到 mm 次项系数。

输出格式
一行 n+m+1n+m+1 个整数,表示乘起来后的多项式的 00 到 n+mn+m 次项系数。

样例一
input

1 2
1 2
1 2 1

output

1 4 5 2

explanation

(1+2x)⋅(1+2x+x2)=1+4x+5x2+2x3(1+2x)⋅(1+2x+x2)=1+4x+5x2+2x3。

限制与约定
0≤n,m≤1050≤n,m≤105,保证输入中的系数大于等于 00 且小于等于 99。

ntt的诞生是为了解决 fft的单位复数根的精度误差

为了实现ntt我们需要找到一个东西使得他和fft里的w相同作用的东西

w有什么作用

ωnn=1ωnn=1
ω0n,ω1n,⋯,ωn−1nωn0,ωn1,⋯,ωnn−1 是互不相同的,这样带入计算出来的点值才可以用来还原出系数
ω2n=ωn2,ωn2+kn=−ωknωn2=ωn2,ωnn2+k=−ωnk,这使得在按照指数奇偶分类之后能够把带入的值也减半使得问题规模能够减半
∑k=0n−1(ωj−in)k={0, n, i≠ji=j
∑k=0n−1(ωnj−i)k={0, i≠jn, i=j
这点保证了能够使用相同的方法进行逆变换得到系数表示
现在我们要在数论中寻找满足这三个性质的数,首先来介绍原根的概念,根据费马定理我们知道,对于一个素数 pp,有下面这样的关系

ap−1≡1(modp)
ap−1≡1(modp)
这一点和单位根 ωω 十分相似,pp 的原根 gg 定义为使得 g0,g1,⋯,gp−2(modp)g0,g1,⋯,gp−2(modp) 互不相同的数

如果我们取素数 p=k⋅2n+1p=k⋅2n+1,并且找到它的原根 gg,然后我们令 gn≡gk(modp)gn≡gk(modp),这样就可以使得 g0n,g1n,⋯,gn−1n(modp)gn0,gn1,⋯,gnn−1(modp) 互不相同,并且 gnn≡1(modp)gnn≡1(modp),这便满足了性质一和性质二

由于 pp 是素数,并且 gnn≡1modpgnn≡1modp,这样 gn2nmodpgnn2modp 必然是 −1−1 或 11,再根据 gkgk 互不相同这个特点,所以 gn2n≡−1(modp)gnn2≡−1(modp),满足性质三

对于性质四,和前面一样也可以验证是满足的,因此再 FNT 中,我们可以用原根替代单位根,这里已经有了一些数 pp 及其原根,可以满足大部分需求

做这道题的时候用到的原根是3 在模数是998244353的情况下 所以在进行idft的时候直接写成mod-1-(mod-1)/(i<<1)即可 避免了再用快速幂进行计算的常数

资料借鉴于:​​http://blog.miskcoo.com/2015/04/polynomial-multiplication-and-fast-fourier-transform#i-15​

#include<cmath>
#include<cstdio>
#include<algorithm>
#define mod 998244353
#define gg 3
#define ll long long
#define N 440000
using namespace std;
inline char gc(){
static char now[1<<16],*S,*T;
if (T==S) {T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
return *S++;
}
inline int read(){
int x=0,f=1;char ch=gc();
while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();}
while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
return x*f;
}
inline int ksm(int base,int t){
int tmp=1;
for (;t;t>>=1,base=(ll)base*base%mod) if (t&1) tmp=(ll)tmp*base%mod;return tmp;
}
int n,m,R[N],a[N],b[N];
inline void ntt(int *x,int f){
for (int i=0;i<n;++i) if (i<R[i]) swap(x[i],x[R[i]]);
for (int i=1;i<n;i<<=1){
ll wn=ksm(gg,f==1?(mod-1)/(i<<1):mod-1-(mod-1)/(i<<1));
for (int j=0;j<n;j+=(i<<1)){
ll w=1;
for (int k=0;k<i;++k,(w*=wn)%=mod){
int t1=x[j+k],t2=x[j+i+k];t2=(ll)t2*w%mod;
x[j+k]=t1+t2>mod?t1+t2-mod:t1+t2;
x[j+i+k]=t1-t2<0?t1-t2+mod:t1-t2;
}
}
}
}
int main(){
// freopen("uoj34.in","r",stdin);
n=read();m=read();
for (int i=0;i<=n;++i) a[i]=read();
for (int i=0;i<=m;++i) b[i]=read();m=n+m;for (n=1;n<=m;n<<=1);int l=log2(n);
for (int i=0;i<n;++i) R[i]=(R[i>>1]>>1)|((i&1)<<l-1);
ntt(a,1);ntt(b,1);
for (int i=0;i<n;++i) a[i]=(ll)a[i]*b[i]%mod;
ntt(a,-1);int inv=ksm(n,mod-2);
for (int i=0;i<n;++i) a[i]=(ll)a[i]*inv%mod;
for (int i=0;i<=m;++i) printf("%d,a[i]);
return 0;
}