【FFT】 利用FFT求卷积(求多项式乘法)

什么是卷积


多项式乘法就是求卷积
1 3 3 4
把这个数组转化成num数组,num[i]表示数字i的有num[i]个。
num = {0 1 0 2 1}
代表0的有0个,1有1个,2有0个,3有2个,4有1个。
使用FFT解决的问题就是num数组和num数组卷积。
num数组和num数组卷积的解决,其实就是从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数,他们的和每个值各有多少个
例如:
{0 1 0 2 1}{0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }
长度为n的数组和长度为m的数组卷积,结果是长度为n+m-1的数组。
{0 1 0 2 1}
{0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }。
这个结果的意义如下:
从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数
取两个数和为 2 的取法是一种:1+1
和为 4 的取法有四种:1+3, 1+3 ,3+1 ,3+1
和为 5 的取法有两种:1+4 ,4+1;
和为 6的取法有四种:3+3,3+3,3+3,3+3,3+3
和为 7 的取法有四种: 3+4,3+4,4+3,4+3
和为 8 的取法有 一种:4+4

关于多项式

  • 次数界:对于多项式A(x),系数为ai,设最高非零系数为ak,任何大于k的整数都是A(x)的次数界
  • 系数表达式:
  • 系数向量:a=(a0,a1,...,an−1)
  • 点值表达方式:{(x0,y0),(x1,y1),...,(xn−1,yn−1)} ,其中xk各不相同,yk=A(xk)

快速傅立叶变换(FFT)

快速计算DFT(离散傅里叶变换)的算法
时间复杂度:O(nlogn)

复数

struct Complex // 复数
{
    double r, i;
    Complex(double _r = 0, double _i = 0) :r(_r), i(_i) {}
    Complex operator +(const Complex &b) {
        return Complex(r + b.r, i + b.i);
    }
    Complex operator -(const Complex &b) {
        return Complex(r - b.r, i - b.i);
    }
    Complex operator *(const Complex &b) {
        return Complex(r*b.r - i*b.i, r*b.i + i*b.r);
    }
};

FFT模板

  • 递归

    Complex* FFT(Complex a[], int n)//n表示向量a的维数
    {
    if(n == 1) return a;
    Complex wn = Complex(cos(2*PI/n), sin(2*PI/n));
    Complex w  = Complex(1, 0);
    Complex* a0 = new Complex[n >> 1]; // new a0[] a1[] size = n/2
    Complex* a1 = new Complex[n >> 1];
    for(int i = 0; i < n; i++) // a0[] = a[even]  a1[] = a[odd]
        if(i & 1) a1[(i - 1) >> 1] = a[i];
        else a0[i >> 1] = a[i];
    Complex *y0, *y1;
    y0 = FFT(a0, n >> 1); // Recursive to solve a0 a1
    y1 = FFT(a1, n >> 1);
    Complex* y = new Complex[n];
    for(int k = 0; k < (n >> 1); k++) //sum y0 y1 -> y
    {
        y[k] = y0[k] + w*y1[k];
        y[k + (n >> 1)] = y0[k] - w*y1[k];
        w = w*wn;
    }
    delete a0;delete a1;delete y0;delete y1;
    return y;
    }
  • 非递归

    void change(Complex y[], int n) // 二进制平摊反转置换 O(logn)  
    {
    int i, j, k;
    for (i = 1,j = n / 2;i < n - 1;i++)
    {
        if (i < j)swap(y[i], y[j]);
        k = n / 2;
        while (j >= k)
        {
            j -= k;
            k /= 2;
        }
        if (j < k)j += k;
    }
    }
    void fft(Complex y[], int n, int on) //FFT:on=1; IFFT:on=-1
    {
    change(y, n);
    for (int h = 2;h <= n;h <<= 1)
    {
        Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h));
        for (int j = 0;j < n;j += h)
        {
            Complex w(1, 0);
            for (int k = j;k < j + h / 2;k++)
            {
                Complex u = y[k];
                Complex t = w*y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w*wn;
            }
        }
    }
    if (on == -1)
        for (int i = 0;i < n;i++)
            y[i].r /= n;
    }

    步骤

  • 补0
    在两个多项式前面补0,得到两个2n次多项式,设系数向量分别为v1和v2。
  • 求值
    使用FFT计算f1=DFT(v1)和f2=DFT(v2)。则f1与f2为两个多项式在2n次单位根处的取值(即点值表示)。
  • 乘法
    把f1与f2每一维对应相乘,得到f,代表对应输入多项式乘积的点值表示。
  • 插值
    使用IFFT计算v=IDFT(f),其中v就是乘积的系数向量。

猜你喜欢

转载自www.cnblogs.com/greenty1208/p/9127929.html
FFT