快速计算多项式相乘系数 FFT快速傅里叶变换
快速傅里叶变换(FFT)——有史以来最巧妙的算法?
正常求两个多项式乘积
A ( x ) = ∑ i = 0 n A i x i , B ( x ) = ∑ i = 0 n B i x i C ( x ) = ∑ i = 0 n ∑ j = 0 n A i B j x i + j A(x)=\sum_{i=0}^{n}{A_ix^i},B(x)=\sum_{i=0}^{n}{B_ix^i} \\ C(x)=\sum_{i=0}^{n}\sum_{j=0}^nA_iB_jx^{i+j} A(x)=i=0∑nAixi,B(x)=i=0∑nBixiC(x)=i=0∑nj=0∑nAiBjxi+j
复杂度为 O ( n 2 ) O(n^2) O(n2)
分别设n+1个在A与B上的点
根据高斯消元法,n+1个不同的点一定能确定一个唯一的n次多项式
A ( x ) = { ( x 0 , A ( x 0 ) ) , ( x 1 , A ( x 1 ) ) , ( x 2 , A ( x 2 ) ) . . . ( x n , A ( x n ) ) } B ( x ) = { ( x 0 , B ( x 0 ) ) , ( x 1 , B ( x 1 ) ) , ( x 2 , B ( x 2 ) ) . . . ( x n , B ( x n ) ) } A(x)=\{(x_0,A(x_0)),(x_1,A(x_1)),(x_2,A(x_2))...(x_n,A(x_n))\}\\ B(x)=\{(x_0,B(x_0)),(x_1,B(x_1)),(x_2,B(x_2))...(x_n,B(x_n))\}\\ A(x)={(x0,A(x0)),(x1,A(x1)),(x2,A(x2))...(xn,A(xn))}B(x)={(x0,B(x0)),(x1,B(x1)),(x2,B(x2))...(xn,B(xn))}
横坐标相同的点相乘
C ( x ) = { ( x 0 , A ( x 0 ) B ( x 0 ) ) , ( x 1 , A ( x 1 ) B ( x 1 ) ) . . . ( x n , A ( x n ) B ( x n ) ) } C(x)=\{(x_0,A(x_0)B(x_0)),(x_1,A(x_1)B(x_1))...(x_n,A(x_n)B(x_n))\} C(x)={(x0,A(x0)B(x0)),(x1,A(x1)B(x1))...(xn,A(xn)B(xn))}
那么就有两个问题:
1.怎么快速得到n+1个点
2.怎么将点值表达法变回系数
如果 A ( x ) A(x) A(x)是一个偶函数,那么 A ( x ) = A ( − x ) A(x)=A(-x) A(x)=A(−x),因此带入一个值也就得到了两个点
同理如果 A ( x ) A(x) A(x)是一个奇函数,那么有 A ( x ) = − A ( − x ) A(x)=-A(-x) A(x)=−A(−x),也可以带一个点得到两个点
因此可以将 A ( x ) A(x) A(x)分成奇函数部分与偶函数部分
P ( x ) = a 0 + a 1 x + a 2 x 2 + . . . + a n x n P ( x ) = ( a 0 + a 2 x 2 + a 4 x 4 + . . . ) + x ( a 1 + a 3 x 2 + a 5 x 4 + . . . ) P ( x ) = P e ( x 2 ) + x P o ( x 2 ) P ( x i ) = P e ( x i 2 ) + x i P o ( x i 2 ) P ( − x i ) = P e ( x i 2 ) − x i P o ( x i 2 ) P(x)=a_0+a_1x+a_2x^2+...+a_nx^n\\ P(x)=(a_0+a_2x^2+a_4x^4+...)+x(a_1+a_3x^2+a_5x^4+...)\\ P(x)=P_e(x^2)+xP_o(x^2)\\ P(x_i)=P_e(x_i^2)+x_iP_o(x_i^2)\\ P(-x_i)=P_e(x_i^2)-x_iP_o(x_i^2) P(x)=a0+a1x+a2x2+...+anxnP(x)=(a0+a2x2+a4x4+...)+x(a1+a3x2+a5x4+...)P(x)=Pe(x2)+xPo(x2)P(xi)=Pe(xi2)+xiPo(xi2)P(−xi)=Pe(xi2)−xiPo(xi2)
然后如果将 P e ( x ) , P o ( x ) P_e(x),P_o(x) Pe(x),Po(x)继续分解
但我们的 P e ( x ) , P o ( x ) P_e(x),P_o(x) Pe(x),Po(x)是通过 P ( x ) = P e ( x 2 ) + x P o ( x 2 ) P(x)=P_e(x^2)+xP_o(x^2) P(x)=Pe(x2)+xPo(x2)得来的,因此取相反数也无法让它们一一符号相反
那么我们取复数呢?
i 2 = ( − i ) 2 = − 1 1 2 = ( − 1 ) 2 = 1 因此有 i , − i , 1 , − 1 : x 1 , − x 1 , x 2 , − x 2 − 1 , 1 : x 1 2 , x 2 2 1 : x 1 4 可解决 n ≤ 4 的问题 i^2=(-i)^2=-1\\ 1^2=(-1)^2=1\\ 因此有\\ i,-i,1,-1:\ x_1,-x_1,x_2,-x_2\\ -1,1:x_1^2,x_2^2\\ 1:x_1^4 \\可解决n\leq4的问题 i2=(−i)2=−112=(−1)2=1因此有i,−i,1,−1: x1,−x1,x2,−x2−1,1:x12,x221:x14可解决n≤4的问题
那么更大的呢?
我们令 w = e 2 π i n = c o s ( 2 π n ) + i s i n ( 2 π n ) w=e^{\frac{2\pi i}{n}}=cos(\frac{2\pi}{n})+isin(\frac{2\pi}{n}) w=en2πi=cos(n2π)+isin(n2π),我们可以得到它的性质正好可以解决该问题
反过来呢?
将 w = e − 2 π i n w=e^{-\frac{2\pi i}{n}} w=e−n2πi,然后给每个数都乘上 1 n \frac{1}{n} n1即可
//初始化每个位置最终到达的位置
void init(int k)
{
int len=1<<k;
for(int i=0;i<len;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
}
//a表示要操作的系数,n表示序列长度
//若flag为1,则表示FFT,为-1则为IFFT(需要求倒数)
void fft(cp *a,int n,int flag){
for(int i=0;i<n;i++)
{
//i小于rev[i]时才交换,防止同一个元素交换两次,回到它原来的位置。
if(i<rev[i])swap(a[i],a[rev[i]]);
}
for(int h=1;h<n;h*=2)//h是准备合并序列的长度的二分之一
{
cp wn=exp(cp(0,flag*pie/h));//求单位根w_n^1
for(int j=0;j<n;j+=h*2)//j表示合并到了哪一位
{
cp w(1,0);
for(int k=j;k<j+h;k++)//只扫左半部分,得到右半部分的答案
{
cp x=a[k];
cp y=w*a[k+h];
a[k]=x+y; //这两步是蝴蝶变换
a[k+h]=x-y;
w*=wn; //求w_n^k
}
}
}
//判断是否是FFT还是IFFT
if(flag==-1)
for(int i=0;i<n;i++)
a[i]/=n;
}