仙人NTT的入门
更详细的NTT在这里
在快速傅里叶变换中,我们利用
wn
w
n
单位复数根实现了消去引理和折半引理
但是由于复数运算的关系,导致精度问题,使人十分捉鸡
那么,有没有什么整数也满足消去引理和折半引理来代替
wn
w
n
单位复数根呢?
这就是所谓的原根
那么什么是原根呢?
定义
p
p
的原根为满足的整数
g
g
,其中,
并且要求
gi≢gj(modp)
g
i
≢
g
j
(
mod
p
)
那么我们用
gi
g
i
生成的横坐标就不会相同,以此来生成点集表示法
现在我们用
gϕ(p)n
g
ϕ
(
p
)
n
来代替
e2πin
e
2
π
i
n
因为我们想利用整数进行计算,那么
ϕ(p)n
ϕ
(
p
)
n
应该为整数,因为当
p
p
为质数时,,那么
p−1≡0(mod2q),n≤2q
p
−
1
≡
0
(
mod
2
q
)
,
n
≤
2
q
消去引理:
wdkdn=wkn
w
d
n
d
k
=
w
n
k
* 证明:
wdkdn=gdkϕ(p)dn=gkϕ(p)n=wkn
w
d
n
d
k
=
g
d
k
ϕ
(
p
)
d
n
=
g
k
ϕ
(
p
)
n
=
w
n
k
折半引理: 若
n
n
为偶数,则次单位复数根的平方的集合就是
n2
n
2
次单位复数根的集合,特别的,每个
n2
n
2
次单位复数根出现两次
* 证明:
(wkn)2=wkn2,k∈[0,n−1]
(
w
n
k
)
2
=
w
n
2
k
,
k
∈
[
0
,
n
−
1
]
[消去引理]
非常nice
所以就和FFT一样一样的啦!
板子!
多项式乘法
#include <bits/stdc++.h>
using namespace std;
const int N = 3000010;
const int G=3,mod=(119<<23)+1;
int n,m,l;
int a[N],b[N],r[N];
int read() {
int ans=0,flag=1;
char ch=getchar();
while((ch>'9' || ch<'0') && ch!='-') ch=getchar();
if(ch=='-') flag=-1,ch=getchar();
while(ch>='0' && ch<='9') ans=ans*10+ch-'0',ch=getchar();
return ans*flag;
}
int ksm(int a,int b) {
int ans=1;
while(b) {
if(b&1) ans=1ll*ans*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ans;
}
void ntt(int *now,int f) {
for(int i=0;i<n;++i)
if(i<r[i]) swap(now[i],now[r[i]]);
for(int i=1;i<n;i<<=1) {
int gn=ksm(G,(mod-1)/(i<<1));
for(int j=0;j<n;j+=(i<<1)) {
int x,y,g=1;
for(int k=0;k<i;++k,g=1ll*g*gn%mod) {
x=now[j+k],y=1ll*g*now[j+k+i]%mod;
now[j+k]=(x+y)%mod;
now[j+k+i]=(x-y+mod)%mod;
}
}
}
if(f!=1) {
int ny=ksm(n,mod-2);
reverse(now+1,now+n);
for(int i=0;i<n;++i) now[i]=1ll*now[i]*ny%mod;
}
}
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();
m+=n;
for(n=1;n<=m;n<<=1) ++l;
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]=1ll*a[i]*b[i]%mod;
ntt(a,-1);
for(int i=0;i<=m;++i)
printf("%d ",a[i]);
return 0;
}