[多項式アルゴリズム(その1)FFT高速フーリエ変換学習ノート変換

私はいくつかを持っている、いくつかの多項式アルゴリズムを分離することを意図し、ブログの混乱の前に長いと感じましたもっと混沌記事

他の多項式アルゴリズムポータル:

[多項式アルゴリズム](パート2)NTTの高速数論変換の研究ノート

[多項式アルゴリズム(その4)FWT高速ウォルシュ学習ノート変換

[多項式アルゴリズム(その5)分割統治FFT研究ノート


\(簡単1.FFT \)

定義

  • FFT \((高速\フーリエ\変換)\)

    中国名:離散高速フーリエ変換

    (フェイクおかしいTLE)


\(Q:\)この事は、それをやってに使用されていますか?

\(A:\)確かに我々はすべて知っている\(FFT \)素早く高精度にそれを掛け見つけます。

使用\(FFT \)の中で行うことができる\(O(nlog_2n)\)素早く2多項式の複雑さ/畳み込み乗算結果内で決定

  • 多項式

    フォームについて

    \ [A(X)= A_ {N-1} X ^ {N-1} + A_ {N-2} X ^ {N-2} + \ドット+ a_0x ^ 0 = \ sum_ {i = 0} ^ {N-1} a_ix ^ i]は\

    式は、多項式と呼ばれます。最大数は、多項式の次数と呼ばれます。

    • 係数を表し

      \(N-1 \)とみなす多項式係数\(N- \)次元ベクトル:

      \ [\ Vecと、A =(A_0、A_1、\ドット、A_ {M-1})\]

      これは、多項式表現の係数であります

    • ドット値を示し、

      以下のための\(N-1 \)多項式\((X)\) \(\ N-)と同じではありません\(X \)一連の点を与えるように置換されている\(\ {(X_0、Y_0 )\ DOTS \} \)一意に決定多項式であってもよい)\((X)\

    • 多項式乗算

      2つの多項式のための\((X)、B (X)\)

      \ [A(X)= \ sum_ {i = 0} ^ {N-1} a_ix ^ iは、B(X)= \ sum_ {i = 0} ^ {N-1} b_ixは^ iは\]

      そこ\(C(X)= A (X)* B(X)\)

      \ [C(X)= \ sum_ {i = 0} ^ {2N-2} \ sum_ {J + K = I} a_jb_kx ^ iは\]


  • 回旋

    二つのベクトルのための\(\ VECのA =(A_0 、A_1、\ドット、A_ {N-1})、\ VEC B =(B_0、B_1、\ドット、B_ {N-1})\)

    そこ畳み込み\(\ VEC A \ otimes \ VECのB = Cの(C_0、C_1、\ドット、C_ {2N-2})\)

    そこ\(c_k = \ sum_ {I 、J} ^ {iはJ = K +を} \制限はa_ib_j \)

    そして、上記の多項式乗算は非常に似ています。


それでは、どの多項式の乗算にそれを計算するには?

1つの明らかなアプローチは、定義によるものである\(O(N ^ 2) \) が算出されます。

しかし、我々は2つのポイント値が示すことを見出した(\ \ {(ax_0、ay_0)、\ DOTS \}、\ {(bx_0、by_0)、\ DOTS \} \)であってもよい\(O(N)\)に乗じた\(C(X)\)式のポイント値。

だから我々はすぐにポイント値多項式表現に回すとバック​​逆にすることができます方法はありませんか?

いくつかの他のものは、あなたの電子メールを残してください \(FFT \)がこれを行うことができます。

\(FFT \)は、おそらく含まれている\(3 \)の手順を:

パート1

多項式\(\ RIGHTARROW \)点が(値を表す\(DFT、O(nlog_2n)\)

パート2

乗算ポイント値を表します(\(O(N)\)

その3

ポイントの値を示し、\(\ RIGHTARROW \) 多項式)\)\(IDFT、O(nlog_2nを


作ります

  • 複数

    実数部の複数の虚数部、例えば、\(3I + 2 \) \が(私は\)虚数単位であり、\(I ^ 2 = \ SQRT {-1} \) )。これは、点またはベクトルとして理解することができる\((2、3)\)

    複雑なアルゴリズム:

    • 追加

      実部と虚部分を追加します

      \((2 + 3I)+(3 + 3I)=(5 + 6I)\)

      加えます

    • 乗算

      类似多项式乘法,在坐标系中直观表现为模长相乘,幅角相加(幅角为\(x\)轴逆时针转动的角度)。

      \((2+3i)* (3+3i)=6+6i+9i+9i^2=(-3+15i)\)

      私

    • 除法

      类似分数的化简

      \(\frac{2+3i}{3+3i}=\frac{(2+3i)* (3-3i)}{(3+3i)* (3-3i)}=\frac{6-6i+9i-9i^2}{9-9i^2}=\frac{15+3i}{18}=\frac{15}{18}+\frac{3i}{18}\)

      图就不画了,太麻烦了


  • 思想

    规定点值表示中的\(n\)\(x\)值为\(n\)个模长为\(1\)的复数。

但是并不是随机的复数,是均匀分布在单位圆(以原点为圆心,半径为\(1\))上的\(n\)个复数,将圆\(n\)等分。

将点从\(0\)开始标号,设第\(0\)个点为\(\omega_n^0\)(和我一起读,\(Omega\sim\)),以此类推。

\((1,0)\)为起点,由复数乘法规则得:\(\omega_n^i\)的模长一定是\(1\)

\(\omega_n^i\)对应的点为\((\cos(\frac{i}{n}2\pi),\sin(\frac{i}{n}2\pi))\)。(采用弧度制)

把这些复数称为\(n\)次单位根。

接下来进入正题。


DFT (Discrete Fourier Transform)

\(Q:\)学了这么多,但是复杂度不还是\(O(n^2)\)吗?

\(A:\)下面就介绍\(O(nlog_2n)\)的算法。

  • \(Cooley-Tukey\)算法

    发明者:\(J.\ W.\ Cooley\&J.\ W.\ Tukey\)

    思想:分治

使\(n=2^m(m\in \mathbb{Z})\),若不够高位用\(0\)补齐(显然没有影响)。

接着,对于多项式\(A(x)=\sum_{i=0}^{n-1}\limits a_ix^i\),将其各项按次数奇偶性分类:

\[A(x)=(a_0x^0+a_2x^2+\dots+a_{n-2}x^{n-2})+(a_1x^1+a_3x^3+\dots+a_{n-1}x^{n-1})\]

现在设:

\[A_1(x)=(a_0x^0+a_2x^1+\dots+a_{n-2}x^{\frac{n-2}2})\]

\[A_2(x)=(a_1x^0+a_3x^1+\dots+a_{n-1}x^{\frac{n-2}2})\]

则有:

\[A(x)=A_1(x^2)+xA_2(x^2)\]

对于\(k<\frac n2,\)有:

\[A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})\]

\[=A_1(\omega_{\frac n2}^k)+\omega_n^kA_2(\omega_{\frac n2}^k)\]

同理,对于\(k+\frac n2\)有:

\[A(\omega_n^{k+\frac n2})=A_1(\omega_n^{2k+n})+\omega_n^{k+\frac n2}A_2(\omega_n^{2k+n})\]

\[=A_1(\omega_{\frac n2}^k*\omega_n^n)+\omega_n^k*\omega_n^{\frac n2}A_2(\omega_{\frac n2}^k*\omega_n^n)\]

因为\(\omega_n^{\frac n2},\omega_n^n\) 分别对于着\((-1,0),(1,0)\),则

\[A(\omega_n^{k+\frac n2})=A_1(\omega_{\frac n2}^k)-\omega_n^kA_2(\omega_{\frac n2}^k)\]

于是,问题被分成了更小的子问题,递归求解即可。

时间复杂度?这不某年初赛题吗 \(T(n)=2T(\frac n2)+O(n)=O(nlog_2n)\)


IDFT (Inverse Discrete Fourier Transform)

\(Q:\)既然把多项式变成了点值表示,那么怎么把它变回去呢??

首先,这个问题相当于解一个线性方程组:

\[\begin{cases}a_0(\omega_n^0)^0\quad+a_1(\omega_n^0)^1\quad+\cdots+a_{n-1}(\omega_n^0)^{n-1}\quad=A(\omega_n^0)\\a_0(\omega_n^1)^0\quad+a_1(\omega_n^1)^1\quad+\cdots+a_{n-1}(\omega_n^1)^{n-1}\quad=A(\omega_n^1)\\\qquad\vdots\qquad\qquad\vdots\qquad\qquad\ddots\qquad\qquad\vdots\qquad\qquad\qquad\vdots\\a_0(\omega_n^{n-1})^0+a_1(\omega_n^{n-1})^1+\cdots+a_{n-1}(\omega_n^{n-1})^{n-1}=A(\omega_n^{n-1})\end{cases}\]

写成矩阵:

\[\begin{bmatrix}(\omega_n^0)^0&(\omega_n^0)^1&\cdots&(\omega_n^0)^{n-1}\\(\omega_n^1)^0&(\omega_n^1)^1&\cdots&(\omega_n^1)^{n-1}\\\vdots&\vdots&\ddots&\vdots\\(\omega_n^{n-1})^0&(\omega_n^{n-1})^1&\cdots&(\omega_n^{n-1})\end{bmatrix}\begin{bmatrix}a_0\\a_1\\\vdots\\a_{n-1}\end{bmatrix}=\begin{bmatrix}A(\omega_n^0)\\A(\omega_n^1)\\\vdots \\A(\omega_n^{n-1})\\\end{bmatrix}\]

求解矩阵逆我会,高斯消元

\(O(n^3)\)是不可能的,这辈子都不可能的。

设上面式子中左边矩阵为\(X\)

现在考虑矩阵\(Y,Y_{i,j}=(\omega_n^{-i})^j,Z=X* Y\)

则:

\[Y=\begin{bmatrix}(\omega_n^{-0})^0&(\omega_n^{-0})^1&\cdots&(\omega_n^{-0})^{n-1}\\(\omega_n^{-1})^0&(\omega_n^{-1})^1&\cdots&(\omega_n^{-1})^{n-1}\\\vdots&\vdots&\ddots&\vdots\\(\omega_n^{-(n-1)})^0&(\omega_n^{-(n-1)})^1&\cdots&(\omega_n^{-(n-1)})\end{bmatrix}\]

\[Z_{i,j}=\sum_{k=0}^{n-1}X_{i,k}Y_{k,j}\]

\[=\sum_{k=0}^{n-1}\omega_n^{ik}\omega_n^{-jk}\]

\[=\sum_{k=0}^{n-1}\omega_n^{k(j-i)}\]

那么当\(i=j\)

\[Z_{i,j}=n\omega_n^0=n\]

否则当\(i\not=j\)

由等比数列求和公式:

\[Z_{i,j}=\frac{a_1-a_n* q}{1-q}\]

\[=\frac{\omega_n^0-\omega_n^{(n-1)* (j-i)}* \omega_n^{j-i}}{1-\omega_n^{j-i}}\]

\[=\frac{1-\omega_n^{n* (j-i)}}{1-\omega_n^{j-i}}\]

\[=\frac{1-1}{1-\omega_n^{j-i}}\]

\[=0\]

那么就得到

\[X* Y=nI\]

\(I\)指单位矩阵)

\[X* \frac 1nY=I\]

\[X^{-1}=\frac 1nY\]

也就是说,我们只要把\(DFT\)过程中的点值选取\(\omega_n^i\)换成\(\omega_n^{-i}\),进行一次\(DFT\)后把结果除以\(n\)就可以了。

时间复杂度证明同上。

那么这就是\(FFT\)的过程了。

是不是很简单啊


代码实现

首先是最基本的\(FFT\)

采用简单的递归实现。

时间复杂度 \(O(nlog_2n)\)

空间复杂度 \(O(nlog_2n)\)

代码:

#include <cmath>
#include <cstdio>

struct Complex//自定义复数,STL太慢
{
    double x,y;//x为实部,y为虚部
    inline Complex operator+(const Complex &a)//加法
        {return (Complex){x+a.x,y+a.y};}
    inline Complex operator-(const Complex &a)//减法
        {return (Complex){x-a.x,y-a.y};}
    inline Complex operator*(const Complex &a)//乘法
        {return (Complex){x*a.x-y*a.y,x*a.y+y*a.x};}
    //除法用不到就没写
}Pol[100005],Tmp[100005],Ome[100005],Inv[100005];
//Pol - 多项式 Tmp - 备用数组 Ome - 预处理\omega_n^i Inv - Ome的逆

int n;//n=2^m
const double PI=acos(-1);

void Pre()
{
    for(int i=0;i<n;++i)
    {
        Ome[i]=(Complex){cos(2.0*PI*i/n), sin(2.0*PI*i/n)};
        Inv[i]=(Complex){cos(2.0*PI*i/n),-sin(2.0*PI*i/n)};
    }//简单的预处理
}

void FFT(int Siz,int Lef,int Len)//Siz - 子问题大小 Lef - 区域最左端 Len - 步长(a0与a1的距离)
{
    if(Siz==1)return;
    int NSiz=Siz>>1;//下一个子问题
    FFT(NSiz,Lef,Len<<1),FFT(NSiz,Lef+NSiz,Len<<1);//递归处理
    for(int i=0;i<NSiz;++i)
    {
        int Pos=Len*i<<1;
        Tmp[i]=Pol[Lef+Pos]+Ome[i*Len]*Pol[Lef+Pos+Len];//按照定义计算
        Tmp[i+NSiz]=Pol[Lef+Pos]-Ome[i*Len]*Pol[Lef+Pos+Len];
    }
    for(int i=0;i<Siz;++i)Pol[Lef+i*Len]=Tmp[i];//计算完毕
}

int main(){Pre();FFT(n=65536,0,1);};

如果是\(IDFT\)\(FFT\)\(Ome\)改成\(Inv\)最后结果\(/n\)即可。


但是。。这个程序常数太大了!!(自带O(Inf)大常数

我们来尝试优化程序。

非递归实现

发现,第一层递归将下标二进制中最后一位相同的元素分在了一起。(按奇偶性分类)

第二层将最后两位相同的分在了一起。

于是,同一组数二进制反转后是一段连续的区间(前几位相同,后几位包含所有情况)。

見つかった、\(私は\)の位置はどこ最後です\(R_iと\) \(私は\)バイナリの逆)

最終的な位置にすべての番号の最初の、そして最後までマージ。

時間複雑\(O(nlog_2n)\)

宇宙複雑\(O(nlog_2n)\)

コード:

#include <cmath>
#include <cstdio>
#include <algorithm>

struct Complex//自定义复数,STL太慢
{
    double x,y;//x为实部,y为虚部
    inline Complex operator+(const Complex &a)//加法
        {return (Complex){x+a.x,y+a.y};}
    inline Complex operator-(const Complex &a)//减法
        {return (Complex){x-a.x,y-a.y};}
    inline Complex operator*(const Complex &a)//乘法
        {return (Complex){x*a.x-y*a.y,x*a.y+y*a.x};}
    //除法用不到就没写
}Pol[100005],Ome[100005],Inv[100005];
//Pol - 多项式 Ome - 预处理\omega_n^i Inv - Ome的逆

int n;//n=2^m
const double PI=acos(-1);

void Pre()
{
    for(int i=0;i<n;++i)
    {
        Ome[i]=(Complex){cos(2.0*PI*i/n), sin(2.0*PI*i/n)};
        Inv[i]=(Complex){cos(2.0*PI*i/n),-sin(2.0*PI*i/n)};
    }//简单的预处理
}

void FFT(Complex op[])
{
    for(int i=0,j=0;i<n;++i)
    {
        if(i>j)std::swap(Pol[i],Pol[j]);//避免重复交换
        for(int l=n>>1;(j^=l)<l;l>>=1);//反向二进制加法
    }
    for(int i=2;i<=n;i<<=1)//现在处理的区间长度(从下往上)
    {
        int m=i>>1;//区间子问题
        for(int j=0;j<n;j+=i)//对每一个区间计算一边
            for(int k=0;k<m;++k)//此区间的左边(k<i/2)
            {
                Complex Tmp=op[n/i*k]*Pol[j+k+m];//避免额外内存开销(蝴蝶操作)
                Pol[j+k+m]=Pol[j+k]-Tmp;
                Pol[j+k]=Pol[j+k]+Tmp;
            }
    }
}

int main(){n=65536;Pre();FFT(Ome);FFT(Inv);};

P3803 [テンプレート]多項式乗算(FFT)

\(JA、\)テンプレートのタイトル。

アップによってがあるので\(N + m個\)回、補うために(N + m個\)\

時間複雑\(O(nlog_2n)\)

宇宙複雑\(O(nlog_2n)\)

コード:

#include <cmath>
#include <cstdio>
#include <cctype>
#include <algorithm>

char File[1000005],*p1=File,*p2=File;

inline char Getchar()
{
    return p1==p2&&(p2=(p1=File)+fread(File,1,1000000,stdin),p1==p2)?EOF:*p1++;
}

inline int Getint()
{
    register int x=0,c;
    while(!isdigit(c=Getchar()));
    for(;isdigit(c);c=Getchar())x=x*10+(c^48);
    return x;
}

struct Complex
{
    double x,y;
    inline Complex operator+(const Complex &a)
        {return (Complex){x+a.x,y+a.y};}
    inline Complex operator-(const Complex &a)
        {return (Complex){x-a.x,y-a.y};}
    inline Complex operator*(const Complex &a)
        {return (Complex){x*a.x-y*a.y,x*a.y+y*a.x};}
}a[3000005],b[3000005],Ome[3000005],Inv[3000005];

int n,m,Maxl;
const double PI=acos(-1);

void Pre()
{
    for(register int i=0;i<n;++i)
    {
        Ome[i]=(Complex){cos(2.0*PI*i/n),sin(2.0*PI*i/n)};
        Inv[i]=(Complex){cos(2.0*PI*i/n),sin(2.0*PI*-i/n)};
    }
}

void FFT(Complex Pol[],Complex op[])
{
    for(int i=0,j=0;i<n;++i)
    {
        if(i>j)std::swap(Pol[i],Pol[j]);
        for(int l=n>>1;(j^=l)<l;l>>=1);
    }
    for(register int i=2;i<=n;i<<=1)
    {
        int m=i>>1;
        for(register int j=0;j<n;j+=i)
            for(register int k=0;k<m;++k)
            {
                Complex Tmp=op[n/i*k]*Pol[j+k+m];
                Pol[j+k+m]=Pol[j+k]-Tmp;
                Pol[j+k]=Pol[j+k]+Tmp;
            }
    }
}

int main()
{
    n=Getint(),m=Getint();
    for(register int i=0;i<=n;++i)a[i].x=Getint();
    for(register int i=0;i<=m;++i)b[i].x=Getint();
    for(Maxl=n+m,n=2;n<=Maxl;n<<=1);
    Pre();
    FFT(a,Ome),FFT(b,Ome);
    for(int i=0;i<n;++i)a[i]=a[i]*b[i];
    FFT(a,Inv);
    for(int i=0;i<=Maxl;++i)printf("%d%c",(int)floor(a[i].x/n+0.5),i==Maxl?'\n':' ');
    return 0;
}

[テンプレート] A * B問題アップグレードバージョン(FFT高速フーリエ変換)

~~私は最終的に*のBアップを書きます!~~

\(Xの\)として\(10 \)の多項式乗算であることができます。

コード:

#include <cmath>
#include <cstdio>
#include <cctype>
#include <algorithm>

char File[1000005],*p1=File,*p2=File;

inline int Getint()
{
    register int c;
    while(!isdigit(c=getchar()));
    return c^48;
}

struct Complex
{
    double x,y;
    inline Complex operator+(const Complex &a)
        {return (Complex){x+a.x,y+a.y};}
    inline Complex operator-(const Complex &a)
        {return (Complex){x-a.x,y-a.y};}
    inline Complex operator*(const Complex &a)
        {return (Complex){x*a.x-y*a.y,x*a.y+y*a.x};}
}a[150005],b[150005],Ome[150005],Inv[150005];

int n,Maxl,s[150005];
const double PI=acos(-1);

void Pre()
{
    for(register int i=0;i<n;++i)
    {
        Ome[i]=(Complex){cos(2.0*PI*i/n),sin(2.0*PI*i/n)};
        Inv[i]=(Complex){cos(2.0*PI*i/n),sin(2.0*PI*-i/n)};
    }
}

void FFT(Complex Pol[],Complex op[])
{
    for(int i=0,j=0;i<n;++i)
    {
        if(i>j)std::swap(Pol[i],Pol[j]);
        for(int l=n>>1;(j^=l)<l;l>>=1);
    }
    for(register int i=2;i<=n;i<<=1)
    {
        int m=i>>1;
        for(register int j=0;j<n;j+=i)
            for(register int k=0;k<m;++k)
            {
                Complex Tmp=op[n/i*k]*Pol[j+k+m];
                Pol[j+k+m]=Pol[j+k]-Tmp;
                Pol[j+k]=Pol[j+k]+Tmp;
            }
    }
}

int main()
{
    scanf("%d",&n),--n;
    for(register int i=n;i>=0;--i)a[i].x=Getint();
    for(register int i=n;i>=0;--i)b[i].x=Getint();
    for(Maxl=n<<1,n=2;n<=Maxl;n<<=1);
    Pre();
    FFT(a,Ome),FFT(b,Ome);
    for(int i=0;i<n;++i)a[i]=a[i]*b[i];
    FFT(a,Inv);
    for(int i=0;i<=Maxl+5;++i)
    {
        s[i]+=(int)floor(a[i].x/n+0.5);
        s[i+1]+=s[i]/10;
        s[i]%=10;
    }
    bool OK=false;
    for(int i=Maxl+5;i>=0;--i)
    {
        if(s[i])OK=true;
        if(OK||!i)putchar(s[i]^48);
    }
    puts("");
    return 0;
}

概要

\(FFT \)ひどいです。リーグは、テストではないだろうが、\((フラグ)\)が、それでも非常に有用なのは、それを統合。

参考:( \(Dalao \ ORZ \) )

高速フーリエ変換は-Miskcooに多項式の乗算から変換します

学生は、FFTを理解することができます! - 胡バニー

おすすめ

転載: www.cnblogs.com/LanrTabe/p/11305604.html