坑逼FFT的入门

请各位看官移至 此处

简单的预备知识

系数表达

用多项式每一项的系数表示多项式

点值表达

n n 个横坐标各不相同的点(x,y)来表示一个次数界为 n n 的多项式

求值

系数表达=>点值表达
O(n2) O ( n 2 ) 霍纳法则

插值

点值表达 => => 系数表达
O(n2) O ( n 2 ) 拉格朗日公式

系数表达式求多项式加,乘法

A={a0,a1,,an1} A = { a 0 , a 1 , ⋯ , a n − 1 }
B={a0,a1,,an1} B = { a 0 ′ , a 1 ′ , ⋯ , a n − 1 ′ }

C=A+B={a0+a0,a1+a1,an1+an1} C = A + B = { a 0 + a 0 ′ , a 1 + a 1 ′ , ⋯ a n − 1 + a n − 1 ′ }
C=AB,cx=xi=0aiaxi C = A ∗ B , c x = ∑ i = 0 x a i ∗ a x − i ′

点值表达式求多项式加,乘法

A={(x0,y0),(x1,y1),,(xn1,yn1)} A = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ⋯ , ( x n − 1 , y n − 1 ) }
B={(x0,y0),(x1,y1),,(xn1,yn1)} B = { ( x 0 , y 0 ′ ) , ( x 1 , y 1 ′ ) , ⋯ , ( x n − 1 , y n − 1 ′ ) }

C=A+B={(x0,y0+y0),(x1,y1+y1),,(xn1,yn1+yn1)} C = A + B = { ( x 0 , y 0 + y 0 ′ ) , ( x 1 , y 1 + y 1 ′ ) , ⋯ , ( x n − 1 , y n − 1 + y n − 1 ′ ) }
C=AB={(x0,y0y0),(x1,y1y1),,(xn1,yn1yn1)} C = A ∗ B = { ( x 0 , y 0 ∗ y 0 ′ ) , ( x 1 , y 1 ∗ y 1 ′ ) , ⋯ , ( x n − 1 , y n − 1 ∗ y n − 1 ′ ) }


显然的,用系数表达式算多项式乘法简直是龟速 O(n2) O ( n 2 )
而使用点值表达式算多项式乘法则快很多 (O(n)) ( O ( n ) )

可以看出主要若使用点值表达式,主要复杂度在求值和插值(均为 O(n2) O ( n 2 ) )
显然如果求值插值不能优化的话其复杂度甚至是不如系数乘法的(常数大)
所以我们可以利用一些特殊的方式来加速

引入单位复数根

n n 次单位复数根是满足wn=1的复数 w w ,n次单位复数根恰有 n n
对于k=0,1,,n1,这些根是 e2πikn e 2 π i k n .( eπi=1,e2πi=1 e π i = − 1 , e 2 π i = 1 )
用复数的指数形式的定义 eui=cos(u)+isin(u) e u i = c o s ( u ) + i ∗ s i n ( u )
可以建立一个以实数为 x x 轴,以虚数为y轴的坐标系
然后这些单位复数根在坐标轴上正好是半径为1的圆上的点
我们将 e2πi1n e 2 π i 1 n 称为主 n n 次单位根,利用它,其他的单位复数根都是其幂次
然后这些单位复数根的乘法就相当于角度的转换
单位复数根之间满足乘法群的性质(modn)

消去引理: wdkdn=wkn w d n d k = w n k
* 证明: wdkdn=(e2πi1dn)dk=(e2πi1n)k=wkn w d n d k = ( e 2 π i 1 d n ) d k = ( e 2 π i 1 n ) k = w n k

推论: wn2n=1 w 2 n n = − 1

折半引理: n n 为偶数,则n次单位复数根的平方的集合就是 n2 n 2 次单位复数根的集合,特别的,每个 n2 n 2 次单位复数根出现两次
* 证明: (wkn)2=wkn2,k[0,n1] ( w n k ) 2 = w n 2 k , k ∈ [ 0 , n − 1 ] [消去引理]

正式开始

对于 n n 次多项式,我们希望取得其在wn0,wn1,,wnn1处的值
相当于求这样一个矩阵:

A0A1...An1 =w0nw0n..w0nw0nw1n..wn1n..w0nwn1n..w(n1)(n1)na0a1...an1 [ A 0 A 1 . . . A n − 1 ]   = [ w n 0 w n 0 ⋯ w n 0 w n 0 w n 1 ⋯ w n n − 1 . . . . . . . . w n 0 w n n − 1 ⋯ w n ( n − 1 ) ( n − 1 ) ] ∗ [ a 0 a 1 . . . a n − 1 ]

其中 Ai A i 为取到 win w n i 时的点值, ai a i 为第 i i 位的系数
A(i)=j=0n1wnijaj

现定义:
A[0](x)=a0+a2x++an2xn22 A [ 0 ] ( x ) = a 0 + a 2 x + ⋯ + a n − 2 x n − 2 2
A[1](x)=a1+a3x++an1xn22 A [ 1 ] ( x ) = a 1 + a 3 x + ⋯ + a n − 1 x n − 2 2
易得:
A(x)=A[0](x2)+A[1](x2) A ( x ) = A [ 0 ] ( x 2 ) + A [ 1 ] ( x 2 )

因为当 x=wkn x = w n k x=wk+n2n x = w n k + n 2 时,两数平方相等
所以说我们的取值集合就缩小了一半
这样一直递归下去
每层有 2d 2 d 个块,每个块有 n2d n 2 d 个取值,所以一共只需计算 n n 次,共log(n)层,总时间复杂度为 O(nlog(n)) O ( n l o g ( n ) )
非常的 nice n i c e
这个过程也叫做 DFT D F T

得到点值表达式以后我们就只需要 O(n) O ( n ) 的点值乘法,这里不再赘述

那么接下来的任务就是插值了,也就是逆 DFT D F T
易得这个矩阵:

C0C1...Cn1 =w0nw0n..w0nw0nw1n..wn1n..w0nwn1n..w(n1)(n1)nc0c1...cn1 [ C 0 C 1 . . . C n − 1 ]   = [ w n 0 w n 0 ⋯ w n 0 w n 0 w n 1 ⋯ w n n − 1 . . . . . . . . w n 0 w n n − 1 ⋯ w n ( n − 1 ) ( n − 1 ) ] ∗ [ c 0 c 1 . . . c n − 1 ]

其中 Ci C i 为取到 win w n i 时的点值, ci c i 为第 i i 位的系数
所以c=CV1
V1V= V − 1 ∗ V = 单位矩阵
所以 V1 V − 1 中位于第 i i 行第j列(均从0开始标号)的元素为 wijnn w n − i j n
所以
ci=1nj=0n1Cj(win)j c i = 1 n ∑ j = 0 n − 1 C j ∗ ( w n − i ) j

是不是和原来的公式很像?
A(i)=j=0n1ajwijn A ( i ) = ∑ j = 0 n − 1 a j ∗ w n i j

只不过 w w 的系数变成了负的
多了一个1n而已
发现也可以 DFT D F T 解决
这样就做完啦!

但是具体代码中还是有丶东西
看注释吧!

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N =100010;
const double pi=acos(-1);
struct Complex {
    double x;
    double y;
    Complex() {};
    Complex(double _x,double _y) { x=_x;y=_y; }
    Complex operator + (const Complex o) { return Complex(x+o.x,y+o.y); }
    Complex operator - (const Complex o) { return Complex(x-o.x,y-o.y); }
    Complex operator * (const Complex o) { return Complex(x*o.x-y*o.y,x*o.y+y*o.x); }
}a[N<<2],b[N<<2];
int r[N<<2];
int n,m,nn;
void fft(Complex *now,int f) {
    for(int i=0;i<n;++i)//按块将其分到一起
        if(i<r[i]) swap(now[i],now[r[i]]);
    for(int i=1;i<n;i<<=1) {//枚举合并的块的大小
        Complex wn(cos(pi/i),f*sin(pi/i));//单位元,若进行逆DFT变换,则反向旋转
        for(int j=0;j<n;j+=(i<<1)) {//枚举头
            Complex w(1,0);
            for(int k=0;k<i;++k,w=w*wn) {//合并,蝴蝶变换
                Complex x=now[j+k],y=w*now[j+k+i];
                now[j+k]=x+y;
                now[j+k+i]=x-y;
            }
        }
    }
    if(f==-1)
        for(int i=0;i<n;++i)
            now[i].x/=n;
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;++i) scanf("%lf",&a[i].x);
    for(int i=0;i<=m;++i) scanf("%lf",&b[i].x);
    m+=n;
    for(n=1;n<=m;n<<=1) nn++;
    for(int i=0;i<n;++i) {r[i]=(r[i>>1]>>1)|((i&1)<<(nn-1));}
    fft(a,1);fft(b,1);
    for(int i=0;i<=n;++i) { a[i]=a[i]*b[i]; }
    fft(a,-1);//逆DFT
    for(int i=0;i<=m;++i)
        printf("%d ",(int)(a[i].x+0.5))//防止精度出锅;
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值