私はいくつかを持っている、いくつかの多項式アルゴリズムを分離することを意図し、ブログの混乱の前に長いと感じましたもっと混沌記事
他の多項式アルゴリズムポータル:
[多項式アルゴリズム](パート2)NTTの高速数論変換の研究ノート
[多項式アルゴリズム(その4)FWT高速ウォルシュ学習ノート変換
\(簡単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);};
\(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 \) )