多项式基本表现形式
A(x)=∑aixi
多项式乘法
A(x)B(x)C(x)=∑aixi=∑bixi=A(x)×B(x)=i=0∑n−1ai⋅xij=1∑n−1bj⋅xj=i=0∑2n−2ci⋅xi
其中
ci=j=0∑iaj⋅bi−j
多项式的表达方法
对于A(x)=∑ai⋅xi,其系数表达就是由多项式系数组成的向量a=(a0,a1,…,an−1)
用系数计算多项式乘法复杂度为O(n2)
对于A(x)=∑aixi,其点值表达是由n个点值组成的集合
{(x0,y0),(x1,y1),…(xn−1,yn−1)}
其中
∀i=j,xi=xj
对于计算A(x)×B(x),我们将表示A(x)和B(x)的点值口占,使每个多项式包含2n个点值对
{(x0,y0),(x1,y1),…,(x2n−1,y2n−1)}{(x0,y0′),(x1,y1′),…,(x2n−1,y2n−1′)}{(x0,y0y0′),(x1,y1y1′),…,(x2n−1,y2n−1y2n−1′)}
拉格朗日插值
A(x)=k=0∑n−1∏j=kxk−xj∏j=kx−xj
本原单位根
满足xn−1=0的x被称作n次单位根
如果我们有一个ω,对于ω0,ω1,…,ωn−1互不相同,就称ω为本原单位根,我们用ωn表示为一个n次本原单位根,且它们均匀地分布在以复平面的原点为圆心的单位半径的圆周上
其中
ωn0=ωnn=1,ωnn/2=−1
利用复数的指数形式定义
eiu=cos(u)+isin(u)
引理
ωnjωnk=ωnj+k=ωn(j+k)modn∀n≥0,k≥0,d>0,ωdndk=ωnk∀n>0,nmod2=0,ωnn/2=w2=−1
DFT
将多项式的系数表示转变为点值表示,设a是长度为n的序列,对于0≤k<n,令
bk=i=0∑n−1ai⋅ωnki
则称b为a的离散傅里叶变换DFT,记作b=DFT(a)
IDFT
将多项式的点值表示转变为系数表示,DFT的逆变换
从b得到a,我们有公式
ak=n1i=0∑n−1bi⋅ωn−ki
证明,将上面两个式子结合
n1i=0∑n−1bi⋅ωn−ki=n1i=0∑n−1ωn−kij=0∑n−1aj⋅ωnij=n1i=0∑n−1aij=0∑n−1ωn−ki⋅ωnij=n1i=0∑n−1aij=0∑n−1ωni(j−k)
对于j=k,有
j=0∑n−1ωni(j−k)=n
对于j=k,因为0≤j,k<n,所以0<∣j−k∣<n,所以ωnj−k=1 ,根据等比数列求和公式得到
j=0∑n−1ωni(j−k)=j=0∑n−1(ωnj−k)i=1−ωj−k1−(ωnj−k)n=1−ωj−k1−(ωnn)j−k=0
于是考虑原式
n1i=0∑n−1bij=0∑n−1ωni(j−k)=ai
蝴蝶操作
时间复杂度为O(nlogn)
设n=2m,我们有一个这样的变换
A(x)A0(x)A1(x)=A0(x2)+xA1(x2)=i=0∑m−1a2i⋅ωni=i=0∑m−1a2i+1⋅ωni
对于0≤k<n,x=ωnk
A(ωnk)A(ωnk+m)=A0(ωmk)+ωnkA1(ωmk)=A0(ωmk)−ωnkA1(ωmk)
这两个结果只有常数项不同,那么可以同时计算两个式子的结果
位逆序置换
我们可以发现,如果每次都对奇偶分离的话,最终我们要处理的顺序,是对于顺序排列的二进制反着输出一遍的结果
for (int i = 0; i < maxn; i++) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << len - 1);
}
我们把序列a按rev的值的顺序排序得到a′
FFT实现
设maxn是第一个大于等于n且是2的整次幂的数
我们对a′中每一对相邻的长度为2k的序列进行蝴蝶操作,其中1≤2k<maxn,就实现了FFT
观察DFT和DFT的逆变换的式子
bkak=i=0∑n−1ai⋅ωnki=n1i=0∑n−1bi⋅ωn−ki
不同点仅两处常数,所以对于FFT和IFFT我们带个参数,实现方法就一致了
void init() {
for (int i = 1; i < maxn; i <<= 1) {
for (int j = 0; j < i; j++) {
w[i + j] = (node){cos(pi * j / i), sin(pi * j / i)};
}
}
}
void fft(node *a, int opt) {
for (int i = 0; i < maxn; i++) {
if (i < rev[i]) swap(a[i], a[rev[i]]);
}
for (int i = 1; i < maxn; i <<= 1) {
for (int j = 0; j < maxn; j += i << 1) {
for (int k = 0; k < i; k++) {
node x = a[j + k], t = (node){w[i + k].x, w[i + k].y * opt} * a[i + j + k];
a[j + k] = x + t;
a[i + j + k] = x - t;
}
}
}
}
考虑核心部分的复杂度,长度为2k的序列共有logn种,我们当作logn层,每一层我们要计算n/2次,总的复杂度就是O(nlogn)
原根
g为p的原根,当且仅当gk,0≤k≤p−2在modp意义下均不相同
当p是质数时,原根一定存在
在modp意义下,g可以说是一个p−1次本原单位根
数论变换 NTT
考虑到FFT有精度问题,但我们可以通过利用原根的不同次方实现FFT,我们可以把FFT过程中的ωnk替换为gnk并不断取模
因为gnk对于0≤k<n有不同的取值,所以上述方案是可行的,也就是g在modp意义下有本原单位根的性质
重点在于这个模数,我们必须选一个质数p,p−1要含有2的幂的因子,并且这个2的幂要大于多项式系数个数n,通常选择形如a⋅2k+1的素数
常见的模数有UOJ模数
998244353=7×17×223+1
还有
1004535809=479×221+1
上面两个数的最小正原根都是3