快速数论变换(fast number-theoretic transform,FNTT)
2024年1月15日 12:55:59 记
大数乘法与多项式乘法(Polynomial Multiplication)
两个大数如和,用多项式表达:
在进行多项式乘法的时候,得到的系数便可以变相表示为两个大整数之积
转为列表
逆序一下,符合高位在左的书写习惯
进位
快速傅里叶变换(Fast Fourier Transform,FFT)
回到开始的多项式,估算原积不超过位,不足位数则用补齐(方便后续递归分治)
正常的乘法,需要对和的每一项都两两相乘,复杂度很高
为了描述这些多项式方程,除了采用系数表示法(个系数),还可以使用描点表示法(个点),例如方程可以用点唯一确定,方程可以用点唯一确定,即具有个系数的次多项式方程可以由个不同的点唯一确定
那么对于次多项式方程
任取个
得到个函数值
为了求这个函数值,可以用矩阵方法表示(用列向量主要是因为横向量太长了Typora写不下)
如若使用编程实现该功能,复杂度依然没有优势
重新回到,wiki上对奇偶函数的代数结构有这样的描述:
偶函数的任何线性组合皆为偶函数,且偶函数会形成一个实数上的向量空间。相似地,奇函数的任何线性组合皆为奇函数,且奇函数亦会形成一个实数上的向量空间,实际上,”所有“实值函数之向量空间为偶函数和奇函数之子空间的直和。换句话说,每个定义域关于原点对称的函数都可以被唯一地写成一个偶函数和一个奇函数的相加:
类似地,将原式奇偶性分解
记
则有
如此一来计算的值只需计算的值即可,观察到与形似,也可以使用同样的逻辑进一步拆分分治,使用递归的逻辑连接起来。
为了更好的利用奇偶性质,的值不任取,如在本例中选取
这样一来
综上写出伪代码如下:
def fft(a): #a = [a_0,a_1,a_2,...,a_{n-1}] #return [f(w^0),f(w^1),f(w^2),...,f(w^{n-1})] if (n==1) return [a_0] pe = [a_0,a_2,...,a_{n-2}] po = [a_1,a_3,...,a_{n-1}] q_o = fft(p_e) q_e = fft(p_e) w = cos(2pi/n) + i sin(2pi/n) for j in [0,1,2,...,n/2-1]: f(w^j) = q_e[j] + w^j q_o[j] f(w^{j+n/2}) = q_e[j] - w^j q_o[j] return [f(w^0),f(w^1),f(w^2),...,f(w^{n-1})]以上,实现了从系数表示到点列表示的过程,即主要是为了实现这个计算
p.s:突然发现这好像就是我当时参加的TACA的原题啊喂
不妨把这个大矩阵记作
想要实现从点列表示转成系数表示,就需要求得这个矩阵的逆矩阵
离散傅里叶变换矩阵(Discrete Fourier Transform matrix,DFTmtx)
离散傅立叶变换矩阵是将离散傅立叶变换以矩阵乘法来表达的一种表示式
DFT矩阵定义
或
注意:此处在指数上是否存在负号区别于之前的,同时此处矩阵有正规化因数
其逆矩阵是
下验证
当且仅当时有
当时有
至此验证完毕
快速傅里叶逆变换(Inverse Fast Fourier Transform,IFFT)
根据上面的式子,我们得到了
那么回到最先的式子
可以有
即
由于
这意味着先前得出的FFT逻辑完全可以把所有的换成便可完成IFFT的过程,再一次实现函数复用
现在剩余一个问题,是一个复数,在不大的情况下计算误差不大,但是当很大,计算误差和三角函数计算时间都是问题,于是要引入有限域
有限域(finite field)
一点儿没学明白,查资料也写不出来多少
大致运算:
如在下,
根据此类性质,可以写出C函数如下
typedef long long int lli;lli ZmodAdd(lli a, lli b){ a += b; return (a<MOD_P) ? a : (a-MOD_P);}
lli ZmodSub(lli a, lli b){ a -= b; return (a>=0) ? a : (a+MOD_P);}
lli ZmodMul(lli a, lli b){ return (a * b) % MOD_P;}
lli ZmodInv(lli a) { //根据费马小定理,乘法逆元唯一存在 //利用扩展欧几里得算法求乘法逆元 lli x,y; gcdext(a,MOD_P,&x,&y); //x mod p 即为所求逆元,x的范围是正负p/2 return (x>=0) ? x : (x+MOD_P);}
lli ZmodDiv(lli a, lli b) { return (a * ZmodInv(b)) % MOD_P;}
lli gcdext(lli a,lli b,lli *x,lli *y){ //扩展欧几里得算法,网上抄的 if(b==0){ *x=1; *y=0; return a; } lli ret=gcdext(b,a%b,x,y); lli t=*x; *x=*y; *y=t-a/b*(*y); return ret;}快速数论变换(fast number-theoretic transform,FNTT)
先前FFT中选取的性质主要有三:
- 所有计算除了系数只需要计算即可,便于计算
- 各不相同
而在一个有限域内,也同样有数可以满足这两条特性,如,的各值如下
这样的也同样符合先前的性质,因此不妨把FFT中的所有运算都转为模数域下的运算,换成整数的
int FNTT(int *pa,int w,List a,int *f){ //FNTT快速数论变换(基于FFT快速傅里叶变换) if (a.n == 1){ f[0] = pa[a.begin]; return 0; } int nOver2 = a.n>>1; int stepBy2 = a.step<<1; List pe = createList(a.begin, stepBy2, nOver2); List po = createList(a.begin+a.step, stepBy2, nOver2); int qe[nOver2]; int qo[nOver2]; FNTT(pa, ZmodMul(w,w), pe, qe); FNTT(pa, ZmodMul(w,w), po, qo); int t = 1; int qo_t; for (int j=0; j<nOver2; j++){ qe_t = ZmodMul(qo[j],t); f[j] = ZmodAdd(qe[j] , qo_t); f[j+nOver2] = ZmodSub(qe[j] , qo_t); t = ZmodMul(w,t); } return 0;}那么如何选择?
对于质数,原根满足,将看作的等价,则其满足相似的性质
p.s:数论知识好多,不会证明;w;
综上,写成完整C语言代码如下
#include <stdio.h>#define MOD_P 84906529typedef long long int lli;
typedef struct { int begin; int step; int n;}List;List createList(int begin,int step,int n) { List newList; newList.begin = begin; newList.step = step; newList.n = n; return newList;}
//Zmod(p)数域计算(c语言好像没有重载运算符,所以表达式看起来会比较麻烦~)lli ZmodAdd(lli a, lli b);lli ZmodSub(lli a, lli b);lli ZmodMul(lli a, lli b);lli ZmodInv(lli a);lli ZmodDiv(lli a, lli b);lli gcdext(lli a,lli b,lli *x,lli *y); //扩展欧几里得算法
int FNTT(int *pa,int w,List a,int *f){ //FNTT快速数论变换(基于FFT快速傅里叶变换) if (a.n == 1){ f[0] = pa[a.begin]; return 0; } int nOver2 = a.n>>1; int stepBy2 = a.step<<1; List pe = createList(a.begin, stepBy2, nOver2); List po = createList(a.begin+a.step, stepBy2, nOver2); int qe[nOver2]; int qo[nOver2]; FNTT(pa, ZmodMul(w,w), pe, qe); FNTT(pa, ZmodMul(w,w), po, qo); int t = 1; int qo_t; for (int j=0; j<nOver2; j++){ qe_t = ZmodMul(qo[j],t); f[j] = ZmodAdd(qe[j] , qo_t); f[j+nOver2] = ZmodSub(qe[j] , qo_t); t = ZmodMul(w,t); } return 0;}
int main() { int w = 213016; int t = ZmodInv(w); int f[16] = {5,4,3,2,1,0,0,0,0,0,0,0,0,0,0,0}; int g[16] = {1,2,3,4,5,0,0,0,0,0,0,0,0,0,0,0}; int ntt_f[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; int ntt_g[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; int h[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; int r[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; List start = createList(0,1,16); FNTT(f,w,start,ntt_f); FNTT(g,w,start,ntt_g); for (int i=0; i<16; i++){ h[i] = ZmodMul(ntt_f[i],ntt_g[i]); } FNTT(h,t,start,r); for (int i=0; i<16; i++){ r[i] = ZmodDiv(r[i],16); } for (int i=0; i<16; i++){ printf("%d ",r[i]); } return 0;}
lli ZmodAdd(lli a, lli b){ a += b; return (a<MOD_P) ? a : (a-MOD_P);}
lli ZmodSub(lli a, lli b){ a -= b; return (a>=0) ? a : (a+MOD_P);}
lli ZmodMul(lli a, lli b){ return (a * b) % MOD_P;}
lli ZmodInv(lli a) { //根据费马小定理,乘法逆元唯一存在 //利用扩展欧几里得算法求乘法逆元 lli x,y; gcdext(a,MOD_P,&x,&y); //x mod p 即为所求逆元,x的范围是正负p/2 return (x>=0) ? x : (x+MOD_P);}
lli ZmodDiv(lli a, lli b) { return (a * ZmodInv(b)) % MOD_P;}
lli gcdext(lli a,lli b,lli *x,lli *y){ //扩展欧几里得算法,网上抄的 if(b==0){ *x=1; *y=0; return a; } lli ret=gcdext(b,a%b,x,y); lli t=*x; *x=*y; *y=t-a/b*(*y); return ret;}参考学习
超硬核FFT快速傅里叶变换讲解,高效进行高精度乘法运算! - Bilibili