快速傅立叶变换

前置知识

离散傅立叶变换(DFT)

快速傅立叶变换(FFT)

X ( k ) = ∑ n = 0 N − 1 x ( n ) e − 2 π i N n k X(k)=\sum_{n=0}^{N-1} x(n)e^{-\frac{2\pi i}{N} nk} X(k)=n=0N1x(n)eN2πink
x ( k ) = 1 N ∑ k = 0 N − 1 x ( n ) e 2 π i N n k x(k)=\frac{1}{N}\sum_{k=0}^{N-1} x(n)e^{\frac{2\pi i}{N} nk} x(k)=N1k=0N1x(n)eN2πink
FFT简单来说就是加速DFT的
显然计算一次DFT的时间复杂度是 O ( N 2 ) O(N^2) O(N2)
而FFT可以让他降为 O ( N log ⁡ N ) O(N\log N) O(NlogN)

推导

W N = e − 2 π i N W_N=e^{-\frac{2\pi i}{N}} WN=eN2πi,也叫旋转因子
X ( k ) = ∑ n = 0 N − 1 x ( k ) W N n k X(k)=\sum_{n=0}^{N-1} x(k)W_{N}^{nk} X(k)=n=0N1x(k)WNnk
然后我们推导一下 W N n k W_{N}^{nk} WNnk的性质
1. W N r N = 1 ( r = 0 , 1 , ⋯   ) W_N^{rN}=1 (r =0,1,\cdots) WNrN=1(r=0,1,)
2.周期性
   W N n ( k + r N ) = W N n k W_N^{n(k+rN)}=W_N^{nk} WNn(k+rN)=WNnk
3.对称性
   W N n k + N 2 = − W N n k W_N^{nk+\frac{N}{2}}=-W_N^{nk} WNnk+2N=WNnk
4. W 2 N 2 n k = W N n k W_{2N}^{2nk}=W_N^{nk} W2N2nk=WNnk

接下来我们正式推导
假设 N N N 2 2 2的整数幂
X ( k ) = ∑ n = 0 N − 1 x ( k ) W N n k = ∑ r = 0 N 2 − 1 x ( 2 r ) W N 2 r k + ∑ r = 0 N 2 − 1 x ( 2 r + 1 ) W N k ( 2 r + 1 ) = ∑ r = 0 N 2 − 1 x ( 2 r ) W N 2 r k + W N k ∑ r = 0 N 2 − 1 x ( 2 r + 1 ) W N 2 r k = ∑ r = 0 N 2 − 1 x ( 2 r ) W N 2 r k + W N k ∑ r = 0 N 2 − 1 x ( 2 r + 1 ) W N 2 r k \begin{aligned} &\quad X(k) \\ &=\sum_{n=0}^{N-1} x(k)W_{N}^{nk} \\ &=\sum_{r=0}^{\frac{N}{2}-1}x(2r)W_{N}^{2rk}+\sum_{r=0}^{\frac{N}{2}-1} x(2r+1)W_{N}^{k(2r+1)} \\ &=\sum_{r=0}^{\frac{N}{2}-1}x(2r)W_{N}^{2rk}+W_{N}^{k}\sum_{r=0}^{\frac{N}{2}-1} x(2r+1)W_{N}^{2rk}\\ &=\sum_{r=0}^{\frac{N}{2}-1}x(2r)W_{\frac{N}{2}}^{rk}+W_{N}^{k}\sum_{r=0}^{\frac{N}{2}-1} x(2r+1)W_{\frac{N}{2}}^{rk} \end{aligned} X(k)=n=0N1x(k)WNnk=r=02N1x(2r)WN2rk+r=02N1x(2r+1)WNk(2r+1)=r=02N1x(2r)WN2rk+WNkr=02N1x(2r+1)WN2rk=r=02N1x(2r)W2Nrk+WNkr=02N1x(2r+1)W2Nrk

   G ( k ) = ∑ r = 0 N 2 − 1 x ( 2 r ) W N 2 r k G(k)=\sum_{r=0}^{\frac{N}{2}-1}x(2r)W_{\frac{N}{2}}^{rk} G(k)=r=02N1x(2r)W2Nrk
   H ( k ) = ∑ r = 0 N 2 − 1 x ( 2 r + 1 ) W N 2 r k H(k)=\sum_{r=0}^{\frac{N}{2}-1}x(2r+1)W_{\frac{N}{2}}^{rk} H(k)=r=02N1x(2r+1)W2Nrk
X ( k ) = G ( k ) + W N k H ( k ) X(k)=G(k)+W_{N}^{k}H(k) X(k)=G(k)+WNkH(k)
利用周期性
G ( k + N 2 ) = ∑ r = 0 N 2 − 1 x ( 2 r ) W N 2 r ( k + N 2 ) = G ( k ) G(k+\frac{N}{2})=\sum_{r=0}^{\frac{N}{2}-1}x(2r)W_{\frac{N}{2}}^{r(k+\frac{N}{2})}=G(k) G(k+2N)=r=02N1x(2r)W2Nr(k+2N)=G(k)
同理
H ( k + N 2 ) = H ( k ) H(k+\frac{N}{2})=H(k) H(k+2N)=H(k)
于是我们得到
X ( k + N 2 ) = G ( k + N 2 ) + W N k + N 2 H ( k + N 2 ) = G ( k ) − W N k H ( k ) X(k+\frac{N}{2})=G(k+\frac{N}{2})+W_{N}^{k+\frac{N}{2}}H(k+\frac{N}{2})=G(k)-W_{N}^{k}H(k) X(k+2N)=G(k+2N)+WNk+2NH(k+2N)=G(k)WNkH(k)
对比
X ( k ) = G ( k ) + W N k H ( k ) X(k)=G(k)+W_{N}^{k}H(k) X(k)=G(k)+WNkH(k)
我们发现,间隔 N 2 \frac{N}{2} 2N X X X,只差了一个负号
利用这个特点,dft原本要算 N N N个,现在只要算一半
根据 N N N 2 2 2的整数幂,我们还可以继续分下去
G ( k ) = ∑ l = 0 N 4 − 1 x ( 4 l ) W N 2 2 l k + ∑ l = 0 N 4 − 1 x ( 2 ( 2 l + 1 ) ) W N 2 ( 2 l + 1 ) k = ∑ l = 0 N 4 − 1 x ( 4 l ) W N 4 l k + W N 2 k ∑ l = 0 N 4 − 1 x ( 4 l + 2 ) W N 4 l k = G G ( k ) + W N 2 k G H ( k ) \begin{aligned} &\quad G(k) \\ &=\sum_{l=0}^{\frac{N}{4}-1} x(4l)W_{\frac{N}{2}}^{2lk}+\sum_{l=0}^{\frac{N}{4}-1} x(2(2l+1))W_{\frac{N}{2}}^{(2l+1)k} \\ &=\sum_{l=0}^{\frac{N}{4}-1} x(4l)W_{\frac{N}{4}}^{lk}+W_{\frac{N}{2}}^{k}\sum_{l=0}^{\frac{N}{4}-1} x(4l+2)W_{\frac{N}{4}}^{lk} \\ &=GG(k)+W_{\frac{N}{2}}^{k}GH(k) \end{aligned} G(k)=l=04N1x(4l)W2N2lk+l=04N1x(2(2l+1))W2N(2l+1)k=l=04N1x(4l)W4Nlk+W2Nkl=04N1x(4l+2)W4Nlk=GG(k)+W2NkGH(k)
H ( k ) = ∑ l = 0 N 4 − 1 x ( 4 l + 1 ) W N 4 l k + W N 2 k ∑ l = 0 N 4 − 1 x ( 4 l + 3 ) W N 4 l k = H G ( k ) + W N 2 k H H ( k ) \begin{aligned} &\quad H(k)\\ &=\sum_{l=0}^{\frac{N}{4}-1} x(4l+1)W_{\frac{N}{4}}^{lk}+W_{\frac{N}{2}}^{k}\sum_{l=0}^{\frac{N}{4}-1} x(4l+3)W_{\frac{N}{4}}^{lk} \\ &=HG(k)+W_{\frac{N}{2}}^{k}HH(k) \end{aligned} H(k)=l=04N1x(4l+1)W4Nlk+W2Nkl=04N1x(4l+3)W4Nlk=HG(k)+W2NkHH(k)
所以,
周期为 N N N的,可以划分为奇偶两组,每组再划分奇偶…
时间复杂度就是 O ( N log ⁡ N ) O(N\log N) O(NlogN)
当然快速傅立叶逆变换非常类似,把 W N W_{N} WN换成他的共轭 W N ‾ \overline{W_{N}} WN就行了
参考代码

#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const double PI=acos(-1);
const int N=(1<<21)+5;//比2n大的2的整数幂
//复数
class Complex{
public:
    double real,image;
    explicit Complex(const double& real=0,const double& image=0):real(real),image(image){}
};
//复数加法
Complex operator+(const Complex& a,const Complex& b){
    return Complex(a.real+b.real,a.image+b.image);
}
//复数减法
Complex operator-(const Complex& a,const Complex& b){
    return Complex(a.real-b.real,a.image-b.image);
}
//复数乘法
Complex operator*(const Complex& a,const Complex& b){
    return Complex(a.real*b.real-a.image*b.image,b.real*a.image+a.real*b.image);
}
Complex temp[N];
/**
 * 快速傅立叶变换
 * @param a 变换数组
 * @param n N
 * @param inv 1为傅立叶变换,-1为傅立叶逆变换
 */
void fft(Complex a[],int n,int inv){
    if(n==1)return;
    int mid=n>>1; //N/2
    //划分奇偶
    for(int i=0;i<mid;++i){
        temp[i]=a[i<<1];
        temp[i+mid]=a[i<<1|1];
    }
    memcpy(a,temp,sizeof(Complex)*n);
 
    fft(a, mid, inv);
    fft(a+mid, mid, inv);
    //exp(2 PI i /N) || exp(-2 PI i /N)
    Complex w_n(cos(2*PI/n), inv * sin(2 * PI / n));
    Complex w(1,0);
    for(int i=0;i<mid;++i){
        Complex w_h=w*a[i+mid];
        //X(k)=G(k)+W_N^k H(k)
        temp[i]=a[i]+w_h;
        //X(k+N/2)=G(k)-W_N^k H(k)
        temp[i+mid]=a[i]-w_h;
 
        w=w*w_n;
    }
    memcpy(a,temp,sizeof(Complex)*n);
}
char s[N];
Complex a[N];
int ans[N];
int main(){
    scanf("%s",s);
    int len1=strlen(s);
    //我这个实部,你可以继续输入虚部
    for(int i=0;i<len1;++i){
        a[i].real=s[i]-'0';
    }
 
    int tot=len1;
    //变成2的n次幂
    int n=1;
    while(n<tot){
        n<<=1;
    }
    //快速傅立叶变换
    fft(a,n,1);
    //快速傅立叶逆变换
    fft(a,n,-1);
 
    for(int i=0;i<n;++i){
        //我这里只有实部,你可以再搞个处理虚部的
        ans[i]+=a[i].real/n+0.5;
    }
   
    return 0;
}

蝴蝶操作

上面的代码是递归的,而我们可以把他改成迭代的
N = 4 N=4 N=4为例
首先我们计算 G ( 0 ) , G ( 1 ) G(0),G(1) G(0),G(1)
G ( 0 ) = x ( 0 ) + W 2 0 x ( 2 ) G(0)=x(0)+W_{2}^{0} x(2) G(0)=x(0)+W20x(2)
G ( 1 ) = x ( 0 ) − W 2 0 x ( 2 ) G(1)=x(0)-W_2^0 x(2) G(1)=x(0)W20x(2)
对应到递归代码里,应该是 4 4 4分成 2 2 2 2 2 2个,然后 2 2 2个分为 1 1 1 1 1 1个( x ( 0 ) , x ( 2 ) x(0),x(2) x(0),x(2),直接return),然后在这里计算fft(a,2,1)
在这里插入图片描述
类似的计算H
在这里插入图片描述
接着计算 X X X
X ( 0 ) = G ( 0 ) + W 4 0 H ( 0 ) X(0)=G(0)+W_{4}^{0} H(0) X(0)=G(0)+W40H(0)
X ( 1 ) = G ( 1 ) + W 4 1 H ( 1 ) X(1)=G(1)+W_{4}^{1} H(1) X(1)=G(1)+W41H(1)
X ( 2 ) = G ( 2 ) − W 4 0 H ( 2 ) X(2)=G(2)-W_{4}^{0} H(2) X(2)=G(2)W40H(2)
X ( 3 ) = G ( 3 ) − W 4 1 H ( 3 ) X(3)=G(3)-W_{4}^{1} H(3) X(3)=G(3)W41H(3)
在这里插入图片描述
可以看出来,
x x x计算 G G G H H H的时候
  旋转因子的 N N N 2 2 2
  可以分为 2 2 2组,组里下标相差 N 4 \frac{N}{4} 4N的加在了一起
G G G H H H计算 X X X的时候
  旋转因子的 N N N 4 4 4
  可以分为 1 1 1组,下标相差 N 2 \frac{N}{2} 2N的加在了一起
旋转因子每次只会循环到 N / 2 N/2 N/2,因为另外 N / 2 N/2 N/2都是利用对称性的
最左边的 x x x是根据 1 1 1次划分奇偶排的

我们接着看 N = 8 N=8 N=8
可以看出来也差不多
x x x计算 G G , G H , H G , H H GG,GH,HG,HH GG,GH,HG,HH
  旋转因子的 N N N 2 2 2
  可以分为 4 4 4组,组里下标相差的加在了一起
G G , G H , H G , H H GG,GH,HG,HH GG,GH,HG,HH计算 G , H G,H GH
  旋转因子的 N N N 4 4 4
  可以分为2组,组里下标相差的加在了一起
G , H G,H G,H计算 X X X
  旋转因子的 N N N 8 8 8
  可以分为 1 1 1组,组里下标相差的加在了一起
旋转因子每次只会循环到 N / 2 N/2 N/2,因为另外 N / 2 N/2 N/2都是利用对称性的
最左边的 x x x是根据 2 2 2次划分奇偶排的
0 1 2 3 4 5 6 7
0 2 4 6 1 3 5 7
0 4 2 6 1 5 3 7
在这里插入图片描述
先解决这个奇偶划分
  0  1  2  3    4   5   6  7
000 001 010 011 100 101 110 111
反过来
  0  1  2  3    4   5   6  7
000 100 010 110 001 101 011 111
  0  4  2  6    1   5   3  7
于是,我们可以写出

rev[i]=rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));

接着就是观察规律了
于是不难写出代码

#include <cstdio>
#include <cstring>
#include <cmath>
#include<iostream>
using namespace std;
const double PI=acos(-1);
const int N=(1<<21)+5;//比2n大的2的整数幂
//复数
class Complex{
public:
    double real,image;
    explicit Complex(const double& real=0,const double& image=0):real(real),image(image){}
};
//复数加法
Complex operator+(const Complex& a,const Complex& b){
    return Complex(a.real+b.real,a.image+b.image);
}
//复数减法
Complex operator-(const Complex& a,const Complex& b){
    return Complex(a.real-b.real,a.image-b.image);
}
//复数乘法
Complex operator*(const Complex& a,const Complex& b){
    return Complex(a.real*b.real-a.image*b.image,b.real*a.image+a.real*b.image);
}
 
int rev[N];
/**
 * 快速傅立叶变换
 * @param a 变换数组
 * @param n N
 * @param inv 1为傅立叶变换,-1为傅立叶逆变换
 */
void fft(Complex a[],int n,int inv){
    for(int i=0;i<n;++i){
        if(i<rev[i]){
            swap(a[i],a[rev[i]]);
        }
    }
    //i=每层的N  /2   (也就是每组要循环多少下)
    for(int i=1;i<n;i<<=1){
        //exp(-2 PI i /N) || exp(2 PI i /N)
        Complex w_n(cos(PI/i),inv*sin(PI/i));
        //j是每组开始下标
        for(int j=0;j<n;j+=(i<<1)){
            Complex w(1,0);
            //k是组里的第几个
            for(int k=0;k<i;++k,w=w*w_n){
                Complex g=a[j+k],w_h=w*a[j+k+i];
                a[j+k]=g+w_h;
                a[j+k+i]=g-w_h;
            }
        }
    }
 
    if(-1 == inv){
        for(int i=0;i<n;++i){
            a[i].real=(int)(a[i].real/n+0.5);
            a[i].image=(int)(a[i].image/n+0.5);
        }
    }
}
char s[N];
Complex a[N];
int main(){
    scanf("%s",s);
    int len1=strlen(s);
    //我这个实部,你可以继续输入虚部
    for(int i=0;i<len1;++i){
        a[i].real=s[i]-'0';
    }
 
    int tot=len1;
    int n=1,len=0;
    while(n<tot){
        n<<=1;
        ++len;
    }
    for(int i=0;i<n;++i){
        rev[i]=rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
    }
    fft(a,n,1);
    //快速傅立叶逆变换
    fft(a,n,-1);
    
    return 0;
}

大数乘法

其实我大一的时候,写大数乘法,不会写,然后就去搜,搜到了fft,给我整不会了
众所周知
F [ f ∗ g ] = F [ f ] F [ g ] \mathfrak{F}\left[f\ast g \right ]=\mathfrak{F}\left[f \right ] \mathfrak{F}\left[g \right ] F[fg]=F[f]F[g]
然后大数乘法的每个系数其实就是卷积,于是
我们可以读入两个大数,然后逆序存储
对他们分别傅立叶变换,然后对应位相乘,最后逆变换回来,就是最终的答案了
注意一下, n n n位数 ∗ m *m m位数,不超过 n + m + 1 n+m+1 n+m+1位数,然后你要把 n + m + 1 n+m+1 n+m+1补到2的幂
以洛谷P1919为例

#include <cstdio>
#include <cstring>
#include <cmath>
#include<iostream>
using namespace std;
const double PI = acos(-1);
const int N = (1 << 21) + 5;//比2n大的2的整数幂
//复数
class Complex {
public:
    double real, image;
    explicit Complex(const double& real = 0, const double& image = 0) :real(real), image(image) {}
};
//复数加法
Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real + b.real, a.image + b.image);
}
//复数减法
Complex operator-(const Complex& a, const Complex& b) {
    return Complex(a.real - b.real, a.image - b.image);
}
//复数乘法
Complex operator*(const Complex& a, const Complex& b) {
    return Complex(a.real * b.real - a.image * b.image, b.real * a.image + a.real * b.image);
}
 
int rev[N];
/**
 * 快速傅立叶变换
 * @param a 变换数组
 * @param n N
 * @param inv 1为傅立叶变换,-1为傅立叶逆变换
 */
void fft(Complex a[], int n, int inv) {
    for (int i = 0; i < n; ++i) {
        if (i < rev[i]) {
            swap(a[i], a[rev[i]]);
        }
    }
    //i=每层的N  /2   (也就是每组要循环多少下)
    for (int i = 1; i < n; i <<= 1) {
        //exp(-2 PI i /N) || exp(2 PI i /N)
        Complex w_n(cos(PI / i), inv * sin(PI / i));
        //j是每组开始下标
        for (int j = 0; j < n; j += (i << 1)) {
            Complex w(1, 0);
            //k是组里的第几个
            for (int k = 0; k < i; ++k, w = w * w_n) {
                Complex g = a[j + k], w_h = w * a[j + k + i];
                a[j + k] = g + w_h;
                a[j + k + i] = g - w_h;
            }
        }
    }
 
    if (-1 == inv) {
        for (int i = 0; i < n; ++i) {
            a[i].real = (int)(a[i].real / n + 0.5);
            //a[i].image=(int)(a[i].image/n+0.5);
        }
    }
}
char s[N];
Complex a[N], b[N];
int ans[N];
 
int main() {
    scanf("%s", s);
    int len1 = 0;
    for (int i = strlen(s) - 1; i >= 0; --i) {
        a[len1++].real = s[i] - '0';
    }
    scanf("%s", s);
    int len2 = 0;
    for (int i = strlen(s) - 1; i >= 0; --i) {
        b[len2++].real = s[i] - '0';
    }
    int tot = len1 + len2 + 1;
    int n = 1, len = 0;
    while (n < tot) {
        n <<= 1;
        ++len;
    }
    for (int i = 0; i < n; ++i) {
        rev[i] = rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
    }
    fft(a, n, 1);
    fft(b, n, 1);
    for (int i = 0; i < n; ++i) {
        a[i] = a[i] * b[i];
    }
    fft(a, n, -1);
 
    for (int i = 0; i < n; ++i) {
        ans[i] += a[i].real;
        ans[i + 1] += ans[i] / 10;
        ans[i] %= 10;
    }
    ++n;
    while (n > 0 && 0 == ans[n])--n;
    while (n >= 0) {
        printf("%d", ans[n]);
        --n;
    }
    printf("\n");
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nightmare004

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值