快速傅里叶变换

快速傅里叶变换(FFT),常用于解答多项式乘法相关内容。
背景故事:
在我们平时计算多项式乘法的时候,我们把第一个多项式的每一项都和第二个多项式的每一项相乘,复杂度为O(n ^ 2),此时我们所使用的表示法就是系数表示法
现在我们可以有一种比较强大的方式:
点值表示法
众所周知,假设f(x)的最高次数为 n1 ,即次数界为 n ,那么我们只要知道了n个不相同的x及f(x)值,就能确定出f(x)的多项式。
有一种算法叫做秦九韶算法,它能得出的结论是:
对于一个n次多项式,至多做n次乘法和n次加法。
所以我们知道了n个点的x值之后,我们在O(n^2)的时间内就能计算出所有的y值,然而如果通过快速傅里叶变换的方法,可以在O(nlogn)的时间内求出所有的y值。
引入新定义:
求值:通过多项式的系数表示法求其点值表示法。
插值:通过多项式的点值表示法求其系数表示法。
显然上面两个定义是互逆的关系,FFT就是用来解决求值的过程的。
引入新定义:
n次单位复根:在复平面内,n次单位复根能把整个平面分成n块。它的严格定义是:满足 ωn=1 的复数 ω 值,它一共有n个,分别为 ωkn(k=0...n1) ,其数值为 e2πik/n
由复数幂的定义,可知:
(ω1n)k=ωkn
它有很多性质:
1.相消引理:

nN,xN,dN,ωdkdn=ωkn

这是显然的,你可以考虑这个复平面等分成6个平面取第2个根和把这个复平面等分成3个平面取第一个根没什么区别。
2.折半引理:
nevennN,(ωk+n/2n)2=(ωkn)2

证明:左边 = ω2k+nn=ω2knωnn=ω2kn=
3.几何级数:
对于在复数域上的x 1 ,有下列等式成立:
k=0nxk=1+x+x2+x3+xn=xn+11x1

嗯其实就是等比数列求和。
4.求和引理:
对于任意:n >= 1 且 n不能整除非零整数k ,有:
i=0n1(ωkn)i=0

证明:
由几何级数的计算过程可知:
i=0n1(ωkn)i=(ωkn)n1ωkn1=(ωnn)k1ωkn1=0

证毕。

接下来我们分析一下如何得到最终的多项式吧。
1.求A的n个单位根的点值,求B的n个单位根的点值
2.点值相乘,得到C的点值。
3.计算C的多项式。

我们先考虑第一步。
我们希望计算多项式:

A(x)=j=0n1ajxj

在n个单位根处的值。
设多项式A以系数形式给出,系数向量 a⃗ =(a0,a1,...,an1) ,定义其结果
y=A(ωkn)=j=0n1ajωkjn

则向量 y⃗ =y0,y1,...,yn1 就是系数向量 a⃗  离散傅里叶变换(DFT),记作:y = DFTn(a) .

也就是说,点值的结果就是DFT,那么我们只需要快速计算DFT的值即可。
如果按照正常的算法,时间复杂度显然是 O(n2) 的,所以有快速傅里叶变换(FFT),它采取的是分治的思想。
我们考虑原来的多项式A(x),定义两个新的次数界为n / 2的多项式:

A[0](x)=a0+a2x+a4x2+...+an2xn/21

A[1](x)=a1+a3x+a5x2+...+an1xn/21

则有
A(x)=A[0](x2)+xA[1](x2)

我们如果只是这样的话是没办法减少计算量的,因为这只是相当于展开。
我们考虑一个显然的事实:
对于能够计算 A(x) 的那两个多项式,实际上它们也是可以用FFT求值的, 它们的次数界减少了一半
我们仔细回忆下单位根的性质,发现:
A(k)=A[0](ωkn)2+ωknA[1](ωkn)2;

A(k+n/2)=A[0](ωk+n/2n)2+ωk+n/2nA[1](ωk+n/2n)2;

化简一下第二个式子,发现:
A(k+n/2)=A[0](ωkn)2ωknA[1](ωkn)2;

嗯……
A(x)=u+xv,A(x+n/2)=uxv.

这个玩意叫做 蝴蝶变换
减少了一半的计算量。
我们来计算下复杂度:
T(n) = 2 * T(n / 2) + n ;
T(n / 2) = 2 * T(n / 4) + n / 2;

显然,复杂度为 nlog2n
我们终于讲完了如何求出n个单位根的点值,现在来说第二步:

把点值相乘。
显而易见的一点是,我们如果有两个多项式A(x),B(x),它们相乘得到多项式C(x),则:

A(k)B(k)=C(k)k

那么我们把n个点值相乘的复杂度自然是O(n)的。
只需要记录c[i] = a[i] * b[i]即可。

接下来我们要做的就是插值了。[忘了定义的请自觉往回翻]
因为我不知道拉格朗日插值公式是什么鬼(划掉),所以我决定还是不讲了。
//别打我别打我……我讲…………
嗯我们考虑把DFT写成矩阵的形式:

y0y1y2y3...yn1=1  111...1 1 ω1nω2nω3nωn1n  1ω2nω4nω6nω2(n1)n  1ω3nω6nω9nω3(n1)n...............1ωn1nω2(n1)nω3(n1)nω(n1)2na0a1a2a3...an1

终于挤下来了。
我们发现这个X的矩阵叫做范德蒙德矩阵。
发现我们现在要求的就是 a⃗  ,即 DFT1n(y)
那么怎么求系数矩阵呢?
则:若有矩阵AB = C,已知C,A则有:B = A1C
证明: A1AB=B=A1C
证毕。
显然的一点是:
范德蒙德矩阵的逆是存在的,我们想求 a⃗  也不是很难,但是,矩阵乘法在这里的复杂度是 O(n2) 的。
对于中间那个矩阵,我们设其为 Vn ,则有:
V1n(j,k)ωkjn/n
证明:
……不想写了。
我们给定逆矩阵 V1n ,可以推导出 DFT1n(y) :
aj=1nk=0n1ykωkjn.

然而对比之前的式子……?
y=A(ωkn)=j=0n1ajωkjn

长得一模一样……
用a数组直接存储a数组 * b数组的点值,对当前的a数组做FFT,对其结果除以n就能得到答案。
但是我们现在的东西还停留在递归阶段,我们知道,可以对这个数组进行元素上的调整,这样我们可以先计算那些子节点,省去了递归的过程,这个过程叫做:
雷(二)德(进)算(制)法
大概意思就是把每一个数的二进制头尾翻转过来:
即100 - > 001这样子……
我们可以知道的一件事情就是:0 - > 7这个序列,在递归到叶子节点之后,是:0,4,2,6,1,5,3,7。我们发现把二进制转过来之后按照顺序排序之后,再反转回来就能得到原来的序列了。
嗯这样的话我们合并相邻的两个,就像 线段树一样,这样问题就成功解决了。
模版请用手写复数类并重载运算符。
模版请用手写复数类并重载运算符。
模版请用手写复数类并重载运算符。
题目:
BZOJ2179:给出两个n位10进制整数x和y,你需要计算x*y。
n <= 60000
//据说FFT不开二倍数组长度会跑得更快
//注意:以上内容不保证其正确性与真实性。
//注意:以上内容不保证其正确性与真实性。
//注意:以上内容不保证其正确性与真实性。
我们可以将两个数相乘写成这种形式:
ci=j=0iajbij

然后直接输出就可以了。

bzoj2179,FFT模版,据说我这样不加注释会运行的更快。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define Rep(i,n) for(int i = 1;i <= n;i ++)
#define Rep_0(i,n) for(int i = 0;i < n;i ++)
#define PI M_PI
using namespace std;
struct Virt  
{  
    double r, i;  

    Virt(double r = 0.0,double i = 0.0)  
    {  
        this->r = r;  
        this->i = i;  
    }  

    Virt operator + (const Virt &x)  
    {  
        return Virt(r + x.r, i + x.i);  
    }  

    Virt operator - (const Virt &x)  
    {  
        return Virt(r - x.r, i - x.i);  
    }  

    Virt operator * (const Virt &x)  
    {  
        return Virt(r * x.r - i * x.i, i * x.r + r * x.i);  
    }  
};
const int M = 131072;
Virt a[M],b[M];
int n,m,BL,rev[M],c[M];
char ch[M >> 1];
void Rader()
{
    for(n = 1;n <= m; n <<= 1,BL ++);
    for (int i = 1; i < n; i ++)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (BL - 1));  
}
void FFT(Virt *a,int ty = 1)
{
    Rep_0(i,n)if(i < rev[i])swap(a[i],a[rev[i]]);
    for(int i = 1;i < n;i <<= 1)
    {
        Virt wn(cos(PI / i),ty * sin(PI / i));
        for(int j = 0;j < n;j += (i << 1))
        {
            Virt w(1,0);
            for(int k = 0;k < i;k ++,w = w * wn)
            {
                Virt u = a[j + k],v = w * a[j + k + i];
                a[j + k] = u + v,a[j + k + i] = u - v;
            }
        }
    }
    if(ty == -1)Rep_0(i,n)a[i] = a[i].r / n;
}
int main ()
{
    scanf("%d",&n);
    scanf("%s",ch);
    Rep_0(i,n)a[i] = ch[n - i - 1] - '0';
    scanf("%s",ch);
    Rep_0(i,n)b[i] = ch[n - i - 1] - '0';
    n --;
    m = n << 1;
    Rader();
    FFT(a),FFT(b);
    for(int i = 0;i <= n;i ++)a[i] = a[i] * b[i];
    FFT(a,-1);
    for(int i = 0;i <= m;i ++)c[i] = (int)(a[i].r + 0.1);
    for(int i = 0;i <= m;i ++)
    {
        if(c[i] >= 10)
        {
            c[i + 1] += c[i] / 10,c[i] %= 10;
            if(i == m)m ++;
        } 
    }
    for(int i = m;i >= 0;i --)printf("%d",c[i]);
    return 0;
}

你可能感兴趣的:(fft)