系数表示:
n
n
n 个系数确定一个
n
−
1
n-1
n−1 次多项式
(
a
0
,
a
1
,
a
2
,
⋯
,
a
n
−
1
)
(a_0,a_1,a_2,\cdots,a_{n-1})
(a0,a1,a2,⋯,an−1)
点值表示:
n
n
n 个点确定一个
n
−
1
n-1
n−1 刺多项式
(
x
0
,
x
1
,
x
1
,
⋯
,
x
n
−
1
)
(
y
0
,
y
1
,
y
1
,
⋯
,
y
n
−
1
)
(x_0,x_1,x_1,\cdots,x_{n-1})\\ (y_0,y_1,y_1,\cdots,y_{n-1})\\
(x0,x1,x1,⋯,xn−1)(y0,y1,y1,⋯,yn−1) 其中满足
y
i
=
∑
j
=
0
n
−
1
a
j
x
i
j
y_i=\sum_{j=0}^{n-1}a_jx_i^j
yi=j=0∑n−1ajxij
多项式的乘法与卷积
多项式乘法
有两个多项式
A
(
x
)
,
B
(
x
)
A(x),B(x)
A(x),B(x),两个多项式相乘结果为
C
(
x
)
C(x)
C(x)。
新的多项式系数可以表示为:
c
k
=
∑
i
+
j
=
k
a
i
b
j
=
∑
i
=
0
k
a
i
b
k
−
i
c_k=\sum_{i+j=k}a_ib_j=\sum_{i=0}^ka_ib_{k-i}
ck=i+j=k∑aibj=i=0∑kaibk−i
多项式乘法得到的结果又可以叫做这两个多项式的卷积,可以用
F
F
T
FFT
FFT 进行快速计算。
⌈
\lceil
⌈快速傅里叶变换
⌋
\rfloor
⌋
F
F
T
FFT
FFT
如果暴力求解两个多项式的卷积,时间复杂度为
O
(
n
m
)
O(nm)
O(nm) 利用
F
F
T
FFT
FFT 可以把时间复杂度降到
O
(
n
log
n
)
O(n\log n)
O(nlogn) 具体分为两个过程:
把
A
(
x
)
、
B
(
x
)
A(x)、B(x)
A(x)、B(x) 的系数表示转化成点值表示,然后用
O
(
n
)
O(n)
O(n) 的方法求得
C
(
x
)
C(x)
C(x) 的点值表示,然后还原成
C
(
x
)
C(x)
C(x) 的系数表示。
那么第一步,我们想要找一个快速的方法把
A
(
x
)
、
B
(
x
)
A(x)、B(x)
A(x)、B(x) 的点值表示求出来。
单位根
F
F
T
FFT
FFT 的方法是找到了一组特殊的点代到多项式当中去,可以快速找到一组点的数值。那组特殊的点就是复数中的单位根。
一个复数可以在复平面上用一个向量来表示。在单位元上的向量都可以成为单位根。 我们把一个单位元分成
n
n
n 份,我们就得到了
n
n
n 个单位根,其中的第
k
k
k 个可以即为
ω
n
k
\omega_n^k
ωnk,并且
ω
n
0
=
1
\omega_n^0=1
ωn0=1
欧拉公式:
e
i
θ
=
c
o
s
θ
+
i
⋅
sin
θ
e^{i\theta}=cos\theta+i\cdot\sin\theta
eiθ=cosθ+i⋅sinθ 于是,
ω
n
k
=
e
2
π
k
n
i
\omega_n^k=e^{2\pi\frac{k}{n}i}
ωnk=e2πnki
性质:
ω
n
k
=
ω
2
n
2
k
\omega_n^k=\omega_{2n}^{2k}
ωnk=ω2n2k
ω
n
k
+
n
2
=
−
ω
n
k
\omega_n^{k+\frac{n}{2}}=-\omega_n^k
ωnk+2n=−ωnk
ω
n
k
⋅
ω
n
l
=
ω
n
k
+
l
\omega_n^k\cdot\omega_n^l=\omega_n^{k+l}
ωnk⋅ωnl=ωnk+l
离散傅里叶变换
D
F
T
DFT
DFT
考虑
n
=
2
b
n=2^b
n=2b 项的多项式
A
(
x
)
A(x)
A(x) 系数表示为
(
a
0
,
a
1
,
⋯
,
a
n
−
1
)
(a_0,a_1,\cdots,a_{n-1})
(a0,a1,⋯,an−1)
我们现在想导入一组单位根
(
ω
n
0
,
ω
n
1
,
⋯
,
ω
n
n
−
1
)
(\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1})
(ωn0,ωn1,⋯,ωnn−1),快速算出
(
A
(
ω
n
0
)
,
A
(
ω
n
1
)
,
⋯
,
A
(
ω
n
n
−
1
)
)
(A(\omega_n^0),A(\omega_n^1),\cdots,A(\omega_n^{n-1}))
(A(ωn0),A(ωn1),⋯,A(ωnn−1)),这个过程成为
D
F
T
DFT
DFT
首先,我们把多项式按奇偶性分类:
A
(
x
)
=
(
a
0
+
a
2
x
2
+
⋯
+
a
n
−
2
x
n
−
2
)
+
x
(
a
1
+
a
3
x
2
+
⋯
+
a
n
−
1
x
n
−
2
)
A(x)=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+x(a_1+a_3x^2+\cdots+a_{n-1}x^{n-2})
A(x)=(a0+a2x2+⋯+an−2xn−2)+x(a1+a3x2+⋯+an−1xn−2) 现在,我们设两个多项式:
A
0
(
x
)
=
a
0
+
a
2
x
1
+
⋯
+
a
n
−
2
x
n
−
2
2
A
1
(
x
)
=
a
1
+
a
3
x
1
+
⋯
+
a
n
−
1
x
n
−
2
2
A0(x)=a_0+a_2x^1+\cdots+a_{n-2}x^{\frac{n-2}{2}}\\ A1(x)=a_1+a_3x^1+\cdots+a_{n-1}x^{\frac{n-2}{2}}
A0(x)=a0+a2x1+⋯+an−2x2n−2A1(x)=a1+a3x1+⋯+an−1x2n−2 于是,原来的多项式就变为:
A
(
x
)
=
A
0
(
x
2
)
+
x
A
1
(
x
2
)
A(x)=A0(x^2)+xA1(x^2)
A(x)=A0(x2)+xA1(x2)
D
F
T
DFT
DFT 的详细讨论
当
0
≤
k
≤
n
2
−
1
0\le k\le \frac{n}{2}-1
0≤k≤2n−1 时,
A
(
ω
n
k
)
=
A
0
(
(
ω
n
k
)
2
)
+
ω
n
k
⋅
A
1
(
(
ω
n
k
)
2
)
A(\omega_n^k)=A0((\omega_n^k)^2)+\omega_n^k\cdot A1((\omega_n^k)^2)
A(ωnk)=A0((ωnk)2)+ωnk⋅A1((ωnk)2) 化简得到:
A
(
ω
n
k
)
=
A
0
(
ω
n
2
k
)
+
ω
n
k
⋅
A
1
(
ω
n
2
k
)
A(\omega_n^k)=A0(\omega_{\frac{n}{2}}^k)+\omega_n^k\cdot A1(\omega_{\frac{n}{2}}^k)
A(ωnk)=A0(ω2nk)+ωnk⋅A1(ω2nk) 可以看到,我们分为了两个子问题,可以递归求解。
那么
n
2
≤
k
+
n
2
≤
n
−
1
\frac{n}{2}\le k+\frac{n}{2}\le n-1
2n≤k+2n≤n−1,我们带入得到:
A
(
ω
n
k
+
n
2
)
=
A
0
(
(
ω
n
k
+
n
2
)
2
)
+
ω
n
k
+
n
2
⋅
A
1
(
(
ω
n
k
+
n
2
)
2
)
A(\omega_n^{k+\frac{n}{2}})=A0((\omega_n^{k+\frac{n}{2}})^2)+\omega_n^{k+\frac{n}{2}}\cdot A1((\omega_n^{k+\frac{n}{2}})^2)
A(ωnk+2n)=A0((ωnk+2n)2)+ωnk+2n⋅A1((ωnk+2n)2) 化简得到:
A
(
ω
n
k
+
n
2
)
=
A
0
(
ω
n
2
k
)
−
ω
n
k
⋅
A
1
(
ω
n
2
k
)
A(\omega_n^{k+\frac{n}{2}})=A0(\omega_{\frac{n}{2}}^k)-\omega_n^k\cdot A1(\omega_{\frac{n}{2}}^k)
A(ωnk+2n)=A0(ω2nk)−ωnk⋅A1(ω2nk)
时间复杂度:
T
(
n
)
=
2
T
(
n
2
)
+
O
(
n
)
=
O
(
n
log
n
)
T(n)=2T(\frac{n}{2})+O(n)=O(n\log n)
T(n)=2T(2n)+O(n)=O(nlogn)
离散傅里叶逆变换
I
D
F
T
IDFT
IDFT
将
D
F
T
DFT
DFT 得到的点值转化为系数,这一过程就叫做
I
D
F
T
IDFT
IDFT
假设我们得到了
A
(
x
)
A(x)
A(x) 的点值,设为
(
d
0
,
d
1
,
⋯
,
d
n
−
1
)
(d_0,d_1,\cdots,d_{n-1})
(d0,d1,⋯,dn−1),我们要获得其多项式
F
(
x
)
F(x)
F(x) 设
c
(
k
)
c(k)
c(k) 为
x
=
ω
n
−
k
x=\omega_n^{-k}
x=ωn−k 时的点值。
c
k
=
∑
i
=
0
n
−
1
d
i
⋅
(
ω
n
−
k
)
i
=
∑
i
=
0
n
−
1
∑
j
=
0
n
−
1
a
j
⋅
(
ω
n
i
)
j
⋅
(
ω
n
−
k
)
i
=
∑
j
=
0
n
−
1
a
j
∑
i
=
0
n
−
1
(
ω
n
j
−
k
)
i
=
∑
j
=
0
n
−
1
a
j
⋅
n
⋅
[
j
=
k
]
=
n
⋅
a
k
c_k=\sum_{i=0}^{n-1}d_i\cdot (\omega_n^{-k})^i=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_j\cdot (\omega_n^i)^j\cdot (\omega_n^{-k})^i=\sum_{j=0}^{n-1}a_j \sum_{i=0}^{n-1}(\omega_n^{j-k})^i=\sum_{j=0}^{n-1}a_j\cdot n\cdot [j=k]=n\cdot a_k
ck=i=0∑n−1di⋅(ωn−k)i=i=0∑n−1j=0∑n−1aj⋅(ωni)j⋅(ωn−k)i=j=0∑n−1aji=0∑n−1(ωnj−k)i=j=0∑n−1aj⋅n⋅[j=k]=n⋅ak
constint MAX =1e7+50;constdouble PI =acos(-1);structcomp{double x,y;comp(double xx =0,double yy =0){x = xx,y = yy;}};
comp operator +(comp a,comp b){returncomp(a.x+b.x , a.y+b.y);}
comp operator -(comp a,comp b){returncomp(a.x-b.x , a.y-b.y);}
comp operator *(comp a,comp b){returncomp(a.x*b.x-a.y*b.y , a.x*b.y+a.y*b.x);}int lim =1;int l,r[MAX];voidFFT(comp *A,int type){for(int i =0;i < lim;++i)if(i < r[i])swap(A[i],A[r[i]]);for(int mid =1;mid < lim;mid<<=1){
comp omg(cos(PI/mid),type *sin(PI/mid));for(int R = mid<<1,j =0;j < lim;j += R){
comp w(1,0);for(int k =0;k < mid;++k,w = w * omg){
comp x = A[j+k],y = w * A[j + mid + k];
A[j + k]= x + y;
A[j + mid + k]= x - y;}}}}
comp aa[MAX],bb[MAX];intmain(){int N,M;N =read();M =read();for(int i =0;i <= N;++i)aa[i].x =read();for(int i =0;i <= M;++i)bb[i].x =read();while(lim <= N + M)lim<<=1,l++;for(int i =0;i < lim;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));FFT(aa,1);FFT(bb,1);for(int i =0;i <= lim;++i)aa[i]= aa[i]* bb[i];FFT(aa,-1);for(int i =0;i <= N + M;++i)printf("%d ",(int)(aa[i].x/lim+0.5));return0;}
⌈
\lceil
⌈快速数论变换
⌋
\rfloor
⌋ (NTT)
求的是多项式乘法的系数取模之后的结果。这里模数应该要是一个质数。
我们希望带入一组幂次以便于更好取模运算。
原根
如果
g
i
m
o
d
p
(
1
≤
i
≤
p
−
1
)
g^i\bmod p (1\le i\le p-1)
gimodp(1≤i≤p−1) 的值互不相同,那么
g
g
g 称为
p
p
p 的原根。 并非所有的数都有原根,且如果一个数存在原根,其个数也不唯一。
事实上,原根存在的重要条件是:
m
=
2
,
4
,
p
a
,
2
p
a
(
a
>
0
)
m=2,4,p^a,2p^a(a>0)
m=2,4,pa,2pa(a>0) 且
a
a
a 为奇素数。 如果这个数有原根,那么一定有
φ
(
φ
(
m
)
)
\varphi(\varphi(m))
φ(φ(m)) 个原根。
原根没有快速求法,只能通过暴力枚举判断,或者查表。 常见的
N
T
T
NTT
NTT 模数是
998244353
998244353
998244353,其原根为
3
3
3
同样的性质
加入模数为
p
p
p,我们发现构造一下原根,得到与单位根有同样的性质的数,即:
ω
n
i
=
g
i
(
p
−
1
)
n
m
o
d
p
\omega_n^i=g^{\frac{i(p-1)}{n}}\bmod p
ωni=gni(p−1)modp 直接将
F
F
T
FFT
FFT 中的原根替换为
N
T
T
NTT
NTT 中的原根。
代码
1.64
s
1.64s
1.64s
constint MAX =1e7+50;
ll qpow(ll a,ll n){/* */ll res =1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}constint P =998244353,G =3,Gi =332748118;int lim =1;int l,r[MAX];voidNTT(ll *A,int type){for(int i =0;i < lim;++i)if(i < r[i])swap(A[i],A[r[i]]);for(int mid =1;mid < lim;mid<<=1){
ll omg =qpow(type ==1? G : Gi ,(P -1)/(mid <<1));for(int j =0;j < lim;j +=(mid <<1)){
ll w =1;for(int k =0;k < mid;++k,w = w * omg % P){int x = A[j+k],y = w * A[j + mid + k]% P;
A[j + k]=(x + y)% P;
A[j + mid + k]=(x - y + P)% P;}}}}
ll aa[MAX],bb[MAX];intmain(){int N,M;N =read();M =read();for(int i =0;i <= N;++i)aa[i]=(read()+ P)% P;for(int i =0;i <= M;++i)bb[i]=(read()+ P)% P;while(lim <= N + M)lim<<=1,l++;for(int i =0;i < lim;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));NTT(aa,1);NTT(bb,1);for(int i =0;i < lim;++i)aa[i]=(aa[i]* bb[i])% P;NTT(aa,-1);
ll iv =qpow(lim,P -2);for(int i =0;i <= N + M;++i)printf("%d ",(aa[i]* iv)% P);return0;}