FFT详解

多项式乘法:

C[i]=A[j]B[ij]

可以在O(n*m)时间内求出

多项式点值表示

n次多项式可以用n+1个二维平面上的点(x,y)来唯一表示

证明

设f(n)为一个n次多项式,g(n)为另一个1次多项式,且其点值表示相同
设其点值表示为x[n+1]
那么有f(x[i])-g(x[i])==0
令l(i)为f(i)-g(i)
可知l(i)零点有n+1个,与代数基本定理(一个 n-1 次多项式在复数域上有且仅有 n-1 个根)相矛盾矛盾
所以点值表示可以唯一表示一个多项式
注:下文的n并不是实际的n,而是>=实际n的第一个形如2^k的数,前面不够的项数可以补0,不影响多项式乘法的计算

DFT离散傅里叶变换

->将多项式的系数表示变为点值表示
直接计算虽然有快速的秦九韶算法,但要求n+1个数的对应函数值,时间复杂度为O(n^2)

FFT快速傅里叶变换

复数

形如x+bi的数为复数,x为其实数部分,bi为其虚数部分,i代表 1

复平面

x轴表示复数的实部,y轴表示复数的虚部的二维平面,称作复平面
这里写图片描述
复数可以用复平面上的向量表示上图的向量就表示复数3+i

单位根

单位根,表示为 eiθ ,可知 eiθ=cosθ+isinθ
这里写图片描述
ω1n 表示n的一次单位根,也就是满足将单位圆n等分的最小正角度对应的向量
也就是 e2πn
ωnn==1

性质一

ωin=ωjnωijn
根据定义 ωin=e2πin ,还是很显然的

性质二

ω2i2n=ωin
根据定义 2π2i2n ,上下同时消去2

性质三

ωin=ωi+n2n
ωi+n2n=cos2π(i+n2)n+isin2π(i+n2)n
=cos(2πin+π)+isin(2πin+π)
=cos2πinisin2πin
=ωin

FFT的实现

考虑将单位根次方带入多项式求点值
对于多项式A(x)
可以将其奇数项和偶数项分开

A(x)==a0+a2x2+...anxn+x(a1+a3x2...+an1xn2)

令奇数项部分为 A1(x)=a1+a3x..+an1xn22 ,
偶数项为 A2(x)=a0+a2x2+...anxn2
那么有
A(x)==A2(x2)+xA1(x2)

带入单位根里就是
A(ωin)=A2(ωin2)+ωinA1(ωin2)

考虑 i<n/2 的情况
A(ωin)=A2(ωin2)+ωinA1(ωin2)

A(ωi+n2n)=A2(ωi+n2n2)+ωi+n2nA1(ωi+n2n2)

A(ωi+n2n)=A2(ωi+n2n2)+ωi+n2nA1(ωi+n2n2)

A(ωi+n2n)=A2(ω2i+nn)ωinA1(ω2i+nn)

A(ωi+n2n)=A2(ω2in)ωinA1(ω2in)

A(ωi+n2n)=A2(ωin2)ωinA1(ωin2)

所以只要知道前一半的数,就可以对应推出后一半的数
可以采用倍增算法

IDFT离散傅里叶反变换

将点值表示重新计算成系数表示
假设di为DFT后 ωin 对应的点值
对di建立多项式,将 ωkn 代入,算出点值表示
Ck ωkn 对应的点值表示
Ck=n1i=0di(ωkn)i
Ck=n1i=0n1j=0aj(ωin)j(ωkn)i
Ck=n1i=0n1j=0aj(ωin)jk
Ck=n1j=0ajn1i=0(ωin)jk
令j-k=l
s(j,k)=n1i=0(ωin)jk
j==k
s(j,k)==n
j!=k
s(j,k)=ω0n+ω(jk)n+ω2(jk)n+...+ω(n1)(jk)n
是公比为 ω(jk)n 的等比数列
s(j,k)=ω0nω(jk)nn1ω(jk)n1
s(j,k)=ωnn(jk)1ω(jk)n1
s(j,k)=1(jk)1ω(jk)n1=0
所以 Ck=n1j=0aj[j==k]n
Ck=akn
Ck/n=ak
所以可以算出 Ck 再倒推 ak

实现

考虑递归实现
只需要在这一层从左儿子区间推出这个区间内容
需要O(n)的辅助空间
但常数比较大
考虑从下面层向上递推
这里写图片描述
其实是按照二进制位反过来排序,再按堆结构递推即可

总结

处理多项式乘法 C(x)=A(x)B(x)
先对A(x)求一下点值表达(DFT),再对B(x)求一下点值表达(DFT),由于点值是可以直接相乘的,可以O(n)做乘法
再对最后求得的C(x)的点值表达做一遍IDFT求出系数表达
时间复杂度O(3*nlogn+n*2)
也就是O(nlogn)

代码:

inline void FFT(C *a,int opt){
    for(int i=0;i<N;i++)
        if(i<r[i])
            swap(a[i],a[r[i]]);//二进制位排序
    for(int i=1;i<N;i<<=1){//子区间长度
        C wn;
        wn.a=cos(opt*pii/i),wn.b=sin(opt*pii/i);
        for(int j=0;j<N;j+=(i<<1)){//区间起点
            C w,x,y;
            w.a=1,w.b=0;
            for(int k=0;k<i;k++,w=w*wn){
                x=a[j+k],y=w*a[i+j+k];
                a[j+k]=x+y;
                a[j+k+i]=x-y;
            }
        }
    }
    if(opt==-1)//IDFT
        for(int i=0;i<N;i++){
            a[i].a/=N;
        }
}

例题:
COGS1473. 很强的乘法问题
设A[i]为一个乘数从后往前数第i位上的数(从0开始)
B[i]为另一个乘数,C[i]为积
由于乘法满足C[i+j]=A[i]*B[j]
对每一位上的数看成系数,做多项式乘法
最后在O(n)进位
代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
using namespace std;
const int maxn=(1<<20);
const double pii=acos(-1);
struct C{
    double a,b;
    C(){a=0,b=0;}
    C operator *(const C &x)const{
        C y;
        y.a=a*x.a-b*x.b;
        y.b=a*x.b+b*x.a;
        return y;
    }
    C operator +(const C &x)const{
        C y;
        y.a=x.a+a;
        y.b=x.b+b;
        return y;
    }
    C operator -(const C &x)const{
        C y;
        y.a=a-x.a;
        y.b=b-x.b;
        return y;
    }
};
C A[maxn],B[maxn];
int r[maxn];
int ans[maxn];
string x,y;
int n,m;
int N;
inline void FFT(C *a,int opt){
    for(int i=0;i<N;i++)
        if(i<r[i])
            swap(a[i],a[r[i]]);
    for(int i=1;i<N;i<<=1){
        C wn;
        wn.a=cos(opt*pii/i),wn.b=sin(opt*pii/i);
        for(int j=0;j<N;j+=(i<<1)){
            C w,x,y;
            w.a=1,w.b=0;
            for(int k=0;k<i;k++,w=w*wn){
                x=a[j+k],y=w*a[i+j+k];
                a[j+k]=x+y;
                a[j+k+i]=x-y;
            }
        }
    }
    if(opt==-1)
        for(int i=0;i<N;i++){
            //printf("%d\n",int(a[i].a));
            a[i].a/=N;
        }
}
int main(){
    freopen("bettermul.in","r",stdin);
    freopen("bettermul.out","w",stdout);
    cin>>x>>y;
    n=x.length(),m=y.length();
    for(int i=0;i<n;i++){
        A[i].a=x[n-i-1]-'0';
        //printf("%d\n",int(A[i].a));
    }
    for(int i=0;i<m;i++){
        B[i].a=y[m-i-1]-'0';
        //printf("%lf\n",B[i].a);
    }
    n=n+m-1;
    int L=0;
    while((1<<L)<n)
        L++;
    N=(1<<L);
    //printf("%d\n",N);
    for(int i=0;i<N;i++){
        r[i]=(r[i>>1]>>1)|((i&1)?(N>>1):0);
        //printf("%d\n",r[i]);
    }
    FFT(A,1);
    FFT(B,1);
    for(int i=0;i<N;i++)
        A[i]=A[i]*B[i];
    FFT(A,-1);
    for(int i=0;i<n;i++)
        ans[i]=int(A[i].a+0.5);
    for(int i=0;i<n;i++){
        ans[i+1]+=ans[i]/10;
        ans[i]%=10;
        if(ans[n]&&i==n-1)
            n++;
    }
    for(int i=n-1;i>=0;i--)
        printf("%d",ans[i]);
return 0;
}

感谢

邓祎明的zhihu

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值