FFT前言
快速傅里叶变换 (fast Fourier transform),即利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称,简称FFT。快速傅里叶变换是1965年由J.W.库利和T.W.图基提出的。采用这种算法能使计算机计算离散傅里叶变换所需要的乘法次数大为减少,特别是被变换的抽样点数N越多,FFT算法计算量的节省就越显著。
FFT(Fast Fourier Transformation) 是离散傅氏变换(DFT)的快速算法。即为快速傅氏变换。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。
FFT(Fast Fourier Transformation),中文名快速傅里叶变换,用来加速多项式乘法
朴素高精度乘法时间,但FFT能的时间解决。
多项式的系数表示法和点值表示法
FFT其实是一个用的时间将一个用系数表示的多项式转换成它的点值表示的算法
多项式的系数表示和点值表示可以互相转换
系数表示法
一个n-1次n项多项式可以表示为
也可以用每一项的系数来表示,即
这就是系数表示法.
点值表示法
- 把多项式放到平面直角坐标系里面,看成一个函数
- 把个不同的代入,会得出个不同的,在坐标系内就是个不同的点
- 那么这个点唯一确定该多项式,也就是有且仅有一个多项式满足
- 理由很简单,把条式子联立起来成为一个有条方程的元方程组,每一项的系数都可以解出来,那么
这就是点值表示法。
高精度乘法下两种多项式表示法的区别
对于两个用系数表示的多项式,我们把它们相乘设两个多项式分别为,我们要枚举A每一位的系数与B每一位的系数相乘,那么系数表示法做多项式乘法时间复杂度。
但两个用点值表示的多项式相乘,只需要的时间。
设两个点值多项式分别为
设它们的乘积是,那么
所以这里的时间复杂度只有一个枚举的
- 朴素系数转点值的算法叫DFT(离散傅里叶变换),点值转系数叫IDFT(离散傅里叶逆变换)
DFT(离散傅里叶变换)
一定注意从这里开始所有的n都默认为2的整数次幂
对于任意系数多项式转点值,当然可以随便取任意n个x值代入计算
但是暴力计算当然是的时间
其实可以代入一组神奇的x,代入以后不用做那么多的次方运算
这些x当然不是乱取的,而且取这些x值应该就是傅里叶的主意了
考虑一下,如果我们代入一些x,使每个x的若干次方等于1,我们就不用做全部的次方运算了
± 1是可以的,考虑虚数的话± i也可以,但只有这四个数远远不够
傅里叶说:这个圆圈上面的点都可以做到
以原点为圆心,画一个半径为1的单位圆,那么单位圆上所有的点都可以经过若干次次方得到1,傅里叶说还要把它给n等分了,比如此时n = 8
橙色点即为n = 8时要取的点,从( 1 , 0 ) 点开始,逆时针从0号开始标号,标到7号
记编号为k的点代表的复数值为,那么由模长相乘,极角相加可知
其中称为n次单位根,而且每一个都可以求出 。
那么 即为我们要代入的
卷积
卷积与傅里叶变换有着密切的关系。利用一点性质,即两函数的傅里叶变换的乘积等于它们卷积后的傅里叶变换,能使傅里叶分析中许多问题的处理得到简化。
其中F为傅里叶变换。
单位根的性质
FFT(快速傅里叶变换)
虽然DFT搞出来一堆很牛逼的作为代入多项式的x值
但只是代入这类特殊x值法的变换叫做DFT而已,还是要代入单位根暴力计算
- DFT还是暴力…
但DFT可以分治来做,于是 FFT(快速傅里叶变换) 就出来了
首先设一个多项式
按下标的奇偶性把分成两半,右边再提一个x
两边好像非常相似,于是再设两个多项式,令
比较明显得出
再设,把 作为x代入(接下来几步变换要多想想单位根的性质)
那么对于的话,代入
如果已知和的值,我们就可以同时知道和的值
现在我们就可以递归分治来求FFT。
每一次回溯时只扫当前前面一半的序列,即可得出后面一半序列的答案n==1时只有一个常数项,直接return
时间复杂度
IFFT(快速傅里叶逆变换)
想一下,我们不仅要会FFT,还要会IFFT(快速傅里叶逆变换)我们把两个多项式相乘 (也叫求卷积),做完两遍FFT也知道了积的多项式的点值表示
可我们平时用系数表示的多项式,点值表示没有意义
- 怎么把点值表示的多项式快速转回系数表示法?
- IDFT暴力做?其实也可以用FFT用的时间做
- 一个多项式在分治的过程中乘上单位根的共轭复数,分治完的每一项除以n即为原多项式的每一项系数
FFT模板
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+10;
const double PI = acos(-1.0);
struct Complex
{
double r,i;
Complex(double _r = 0,double _i = 0)
{
r = _r; i = _i;
}
Complex operator +(const Complex &b)
{
return Complex(r+b.r,i+b.i);
}
Complex operator -(const Complex &b)
{
return Complex(r-b.r,i-b.i);
}
Complex operator *(const Complex &b)
{
return Complex(r*b.r-i*b.i,r*b.i+i*b.r);
}
};
void change(Complex* y,int len)
{
int i,j,k;
for(i = 1, j = len/2;i < len-1;i++)
{
if(i < j)swap(y[i],y[j]);
k = len/2;
while( j >= k)
{
j -= k;
k /= 2;
}
if(j < k)j += k;
}
}
void fft(Complex* y,int len,int on)
{
change(y,len);
for(int h = 2;h <= len;h <<= 1)
{
Complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
for(int j = 0;j < len;j += h)
{
Complex w(1,0);
for(int k = j;k < j+h/2;k++)
{
Complex u = y[k];
Complex t = w*y[k+h/2];
y[k] = u+t;
y[k+h/2] = u-t;
w = w*wn;
}
}
}
if(on == -1)
for(int i = 0;i < len;i++)
y[i].r /= len;
}
Complex x1[N<<2];
Complex x2[N<<2];
int n,m,a[N],b[N];
int num[N<<1];
int main(void){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
scanf("%d",&a[i]);
for(int i=0;i<=m;i++)
scanf("%d",&b[i]);
int len,len1;
//len1=n+m+1;
len=1;
while(len < ((n+1)<<1) || len < ((m+1)<<1)) len <<= 1;
for(int i = 0; i <= n; i++)
x1[i] = Complex(a[i],0);
for(int i = n+1; i < len; i++)
x1[i] = Complex(0,0);
for(int i = 0; i <= m; i++)
x2[i] = Complex(b[i],0);
for(int i = m+1; i < len; i++)
x2[i] = Complex(0,0);
fft(x1,len,1);
fft(x2,len,1);
for(int i = 0;i < len;i++)
x1[i] = (x1[i]*x2[i]);
fft(x1,len,-1);
for(int i = 0;i <= n+m;i++)
num[i] = (x1[i].r+0.5);
for(int i=0;i<=n+m;i++)
printf("%d%c",num[i],i==n+m?'\n':' ');
return 0;
}
FFT板子
题目链接:https://www.luogu.com.cn/problem/P1919
递归版
#include<bits/stdc++.h>
using namespace std;
const double pi = acos(-1);
const int N = 3e6+5;
int ans[N];
struct Complex{
double x,y;
Complex(double x = 0,double y = 0):x(x),y(y) { }
}A[N],B[N];
Complex operator * (Complex X,Complex Y)
{
return Complex(X.x*Y.x-X.y*Y.y,X.x*Y.y+X.y*Y.x);
}
Complex operator + (Complex X,Complex Y)
{
return Complex(X.x+Y.x,X.y+Y.y);
}
Complex operator - (Complex X,Complex Y)
{
return Complex(X.x-Y.x,X.y-Y.y);
}
void FFT(Complex *a,int n,int flag)
{
if(n==1)
return ;
Complex a1[n>>1],a2[n>>1];
for (int i=0;i<=n;i+=2)
a1[i>>1] = a[i],a2[i>>1] = a[i+1];
FFT(a1,n>>1,flag);
FFT(a2,n>>1,flag);
Complex wn = Complex(cos(2*pi/n),flag*sin(2*pi/n)),w = Complex(1,0);
for (int i=0;i<(n>>1);i++,w=w*wn)
{
a[i] = a1[i]+w*a2[i];
a[i+(n>>1)] = a1[i]-w*a2[i];
}
}
int main ()
{
string s1,s2;
cin>>s1>>s2;
int l1 = s1.size(),l2 = s2.size();
for (int i=0;i<l1;i++)
A[i] = Complex(s1[l1-i-1]-'0',0);
for (int i=0;i<l2;i++)
B[i] = Complex(s2[l2-i-1]-'0',0);
int n = 1;
while(n<l1+l2-1)n<<=1;
FFT(A,n,1);
FFT(B,n,1);
for (int i=0;i<=n;i++)
A[i] = A[i]*B[i];
FFT(A,n,-1);
for (int i=0;i<n;i++)
{
ans[i]+=A[i].x/n+0.5;
ans[i+1] += ans[i]/10;
ans[i]%=10;
}
while(!ans[n]&&n>=0)
n--;
if(n==-1)
puts("0");
else
for (int i=n;i>=0;i--)
cout<<ans[i];
return 0;
}
迭代版
#include<bits/stdc++.h>
using namespace std;
const int N = 3e6+5;
const double pi = acos(-1);
struct Complex{
double x,y;
Complex(double x1=0,double y1=0){
x = x1, y = y1;
}
Complex operator *(const Complex& C) const{
return Complex(x*C.x-y*C.y,x*C.y+y*C.x);
}
Complex operator +(const Complex& C) const{
return Complex(x+C.x,y+C.y);
}
Complex operator -(const Complex& C) const{
return Complex(x-C.x,y-C.y);
}
}a[N],b[N];
int ans[N],rev[N];
void FFT(Complex *a,int n,int flag)
{
for (int i=0;i<n;i++)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for (int mid=1;mid<n;mid<<=1)
{
Complex wn = Complex(cos(pi/mid),flag*sin(pi/mid));
for (int len=mid<<1,pos=0;pos<n;pos+=len)
{
Complex w = Complex(1,0);
for (int k=0;k<mid;k++,w=w*wn)
{
Complex x = a[k+pos];
Complex y = w*a[k+pos+mid];
a[k+pos] = x+y;
a[k+pos+mid] = x-y;
}
}
}
if(flag==-1)
for (int i=0;i<n;i++)
a[i].x/=n;
}
int main ()
{
string s1,s2;
cin>>s1>>s2;
int l1 = s1.size(),l2 = s2.size();
for (int i=0;i<l1;i++)
a[i] = Complex(s1[l1-i-1]-'0',0);
for (int i=0;i<l2;i++)
b[i] = Complex(s2[l2-i-1]-'0',0);
int n=1,l=0;
while(n<l1+l2-1)n<<=1,l++;
for (int i=0;i<n;i++)
rev[i] = (rev[i>>1]>>1)|((i&1)<<(l-1));
FFT(a,n,1);
FFT(b,n,1);
for (int i=0;i<n;i++)
a[i] = a[i]*b[i];
FFT(a,n,-1);
for (int i=0;i<n;i++)
{
ans[i] += a[i].x+0.5;
ans[i+1] += ans[i]/10;
ans[i] %= 10;
}
while(!ans[n]&&~n)n--;
if(n==-1)
puts("0") ;
else
{
for (int i=n;i>=0;i--)
cout<<ans[i];
}
return 0;
}
三步变两步优化
#include<bits/stdc++.h>
using namespace std;
const int N = 3e6+5;
const double pi = acos(-1);
struct Complex{
double x,y;
Complex(double x1=0,double y1=0){
x = x1, y = y1;
}
Complex operator *(const Complex& C) const{
return Complex(x*C.x-y*C.y,x*C.y+y*C.x);
}
Complex operator +(const Complex& C) const{
return Complex(x+C.x,y+C.y);
}
Complex operator -(const Complex& C) const{
return Complex(x-C.x,y-C.y);
}
}a[N],b[N];
int ans[N],rev[N];
void FFT(Complex *a,int n,int flag)
{
for (int i=0;i<n;i++)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for (int mid=1;mid<n;mid<<=1)
{
Complex wn = Complex(cos(pi/mid),flag*sin(pi/mid));
for (int len=mid<<1,pos=0;pos<n;pos+=len)
{
Complex w = Complex(1,0);
for (int k=0;k<mid;k++,w=w*wn)
{
Complex x = a[k+pos];
Complex y = w*a[k+pos+mid];
a[k+pos] = x+y;
a[k+pos+mid] = x-y;
}
}
}
if(flag==-1)
for (int i=0;i<n;i++)
a[i].x/=n,a[i].y/=n;//优化的变化
}
int main ()
{
string s1,s2;
cin>>s1>>s2;
int l1 = s1.size(),l2 = s2.size();
for (int i=0;i<l1;i++)
a[i].x = s1[l1-i-1]-'0';
for (int i=0;i<l2;i++)
a[i].y = s2[l2-i-1]-'0';
int n=1,l=0;
while(n<l1+l2-1)n<<=1,l++;
for (int i=0;i<n;i++)
rev[i] = (rev[i>>1]>>1)|((i&1)<<(l-1));
FFT(a,n,1);//只需要一次FFT
for (int i=0;i<n;i++)
a[i] = a[i]*a[i];
FFT(a,n,-1);
for (int i=0;i<n;i++)
{
ans[i] += a[i].y/2+0.5;//取出虚数除以2
ans[i+1] += ans[i]/10;
ans[i] %= 10;
}
while(!ans[n]&&~n)n--;
if(n==-1)
puts("0") ;
else
{
for (int i=n;i>=0;i--)
cout<<ans[i];
}
return 0;
}
最详细的博客链接:
https://fanfansann.blog.csdn.net/article/details/111710443
刷过FFT的题目:
https://www.luogu.com.cn/problem/P1919
https://www.luogu.com.cn/problem/P3803
https://ac.nowcoder.com/acm/contest/11166/H
AC代码:
/*
H题:Hash Function
标签:简单数论,卷积,FFT/NTT
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5, maxn = 1<<20;
const double pi = acos(-1);
struct Complex{
double x,y;
Complex(double x1=0,double y1=0){
x = x1, y = y1;
}
Complex operator * (const Complex &C) {
return Complex(x*C.x-y*C.y,x*C.y+y*C.x);
}
Complex operator + (const Complex &C) {
return Complex(x+C.x,y+C.y);
}
Complex operator - (const Complex &C) {
return Complex(x-C.x,y-C.y);
}
}a[maxn],b[maxn];
bool s[maxn];
int rev[maxn];
void FFT(Complex *a,int n,int flag)
{
for (int i=0;i<n;i++)
if(i<rev[i])
swap(a[i],a[rev[i]]);
for (int mid=1;mid<n;mid<<=1)
{
Complex wn = Complex(cos(pi/mid),flag*sin(pi/mid));
for (int len=mid<<1,pos=0;pos<n;pos+=len)
{
Complex w = Complex(1,0);
for (int k=0;k<mid;k++,w=w*wn)
{
Complex x = a[k+pos], y = w*a[k+pos+mid];
a[k+pos] = x+y;
a[k+pos+mid] = x-y;
}
}
}
if(flag==-1)
for (int i=0;i<n;i++)
a[i].x/=n;
}
int main ()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
a[x].x = 1;
b[N-x].x = 1;
}
for (int i=0;i<maxn;i++)
rev[i] = (rev[i>>1]>>1)|((i&1)<<19);
FFT(a,maxn,1);
FFT(b,maxn,1);
for (int i=0;i<maxn;i++)
a[i] = a[i]*b[i];
FFT(a,maxn,-1);
for (int i=0;i<maxn;i++)
{
int x = a[i].x+0.5;
if(x>0)
s[abs(N-i)] = 1;
}
for (int i=n;i<N;i++)
{
int f = 1;
for (int j=i;j<N;j+=i)
if(s[j])
{
f=0;
break;
}
if(f)
{
printf("%d",i);
break;
}
}
return 0;
}