多项式乘法 快速傅里叶变换(FFT)

前置知识1

1.多项式:一个以x为变量的多项式定义在一个代数域F上,将函数A(x)表示为形式和:

A(x) = i=0n1aixi

2.多项式的系数表示法;即由多项式的系数组成的向量 a =(a0, a1, ... an1)

2.多项式的点值表示法:即一个由n的点组成的集合

{ (x0,y0), (x1,y1), ..., (xn1,yn1) }

其中满足对于 i [0, n1]

yi=A(xi)

3.点值表示法的多项式乘法:
已知两个多项式的点值表示:

{ (x0,y0), (x1,y1), ..., (xn1,yn1) }{ (x0,y0), (x1,y1), ..., (xn1,yn1) }

则两式相乘后的点值表示为:

{ (x0,y0y0), (x1,y1y1), ..., (xn1,yn1yn1) }

4.;离散傅里叶变换(DFT):我们称向量 y  = (y0, y1, ... yn1) 为向量 a =(a0, a1, ... an1)的离散傅里叶变换,记为 y =DFT( a ), 同时,a =DFT1( y )

快速傅里叶变换(FFT):

可以在O(n log n)的时间复杂度内求解两个次数界为n的多项式的乘法。算法主要由三步构成:

1.求值O(n log n):将多项式由系数表示法转化为点值表示法,即DFT

2.点值乘法O(n):将两个多项式的点值相乘,得到所求多项式的点值表达式。

3.插值O(n log n):将所求多项式由点值表示法转变为系数表示法,即DFT1

一个次数界为nm的多项式相乘,需要n+m个点的点值表达式,朴素的求法为O(n2),显然x的取值是任意的,我们可以通过选取特殊的x来降低求值的复杂度。

n次单位复数根:

是指满足wn=1的复数w,共有n个,分别为

wnk=e2πik/n(k[0,n1])

根据欧拉公式可得:

wnk=cos2kπn+isin2kπn

n次单位复数根有几个性质:

性质1: wpkpn=wkn

性质2: wnk+n2=wnk

性质3: 对任意n1和不能被n整除的非负整数k,有:

i=0n1 (wnk)i=0

FFT的过程:

1.求值

(以下假定n为2的整数次幂)

对于多项式

A(x)=a0+a1x+a2x2+...+an1xn1

将其按照奇偶项拆分:

A1(x)=a0+a2x+a4x2+...+an2xn21A2(x)=a1+a3x+a5x2+...+an1xn21

则:

A(x)=A1(x2)+xA2(x2)

A(x)wn0,wn1,...,wnn1处的值,即为求A1(x)A2(x)(wn0)2,(wn1)2,...,(wnn1)2处的值。

根据性质1可得:(wnk)2=wn/2k,因此(wn0)2,(wn1)2,...,(wnn1)2这n个单位复数根仅是由n/2个不同的值组成的,我们可以先求出wn/20,...,wn/2n/21n/2个值,再根据性质2,求出后一半的值,于是问题的规模缩小了一半,使用分治的方法即可在O(n log n)的时间内求值。

2.插值

DFT的过程写成矩阵乘法的形式,则可得

y=Vna

其中Vnx=wn时的范德蒙德矩阵。则:

a=Vn1y

根据范德蒙德矩阵的性质,易得Vn1(i,j)处的元素为wnij / n 则可知:

aj=1nk=0n1 ykwnkj

式子与上面求值的DFT相似,通过类似方法也可以在O(n log n)的时间内插值。

详细证明

模板

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long lld;
const int N = 1000005;
const double PI = acos(-1);
typedef complex <double> comp;
void FFT(int M, vector <comp> &a, int typ) {
    if(M == 1) return ;
    vector <comp> a1(M / 2 + 2), a2(M / 2 + 2);
    int cnt = 0;
    for(int i = 0; i <= M; i += 2) {
        a1[cnt] = a[i]; a2[cnt++] = a[i + 1];
    }
    FFT(M >> 1, a1, typ); FFT(M >> 1, a2, typ);
    comp wn(cos(PI * 2 / M), typ * sin(PI * 2 / M)), w(1, 0);
    for(int i = 0; i < (M >> 1); i++) {
        a[i] = a1[i] + w * a2[i];
        a[i + (M >> 1)] = a1[i] - w * a2[i];
        w = w * wn;
    }
}
int n, m, M;
int main() {
    // freopen("data.in", "r", stdin);
    cin >> n >> m;
    M = 1; while(M <= n + m) M <<= 1;
    vector <comp> a(M + 2), b(M + 2);
    for(int i = 0; i <= n; i++) {
        double x; cin >> x; 
        a[i] = complex <double> (x, 0);
    }
    for(int i = 0; i <= m; i++) {
        double x; cin >> x;
        b[i] = complex <double> (x, 0);
    }
    int M = 1; while(M <= n + m) M <<= 1;
    
    FFT(M, a, 1); FFT(M, b, 1);
    vector <comp> c(M + 2);
    for(int i = 0; i <= M; i++) {
        c[i] = a[i] * b[i];
    }
    FFT(M, c, -1);
    for(int i = 0; i <= n + m; i++) {
        cout << (int)(c[i].real() / M + 0.5) << " ";
    }
    return 0;
}
posted @   Mcggvc  阅读(119)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示