赞
踩
目录
定义可以自行百度。通俗点来说,FFT就是利用某些奇偶特点,进行DFT(离散傅里叶变换)和IDFT(离散傅里叶逆变换)的快速求解算法。
(1)该算法在信号学中有很大用处;
(2)在信息学竞赛中:加速多项式乘法,高精度大数运算等;
已知多项式:
A(x)=a0+a1∗x+a2∗x2+....+a(n−1)∗xn−1
B(x)=b0+b1∗x+b2∗x2+....+b(n−1)∗xn−1 现在要计算两个n项的n-1次多项式相乘,其结果肯定是2n-1项的2n-2次多项式,求出相乘多项式幂次由低到高的系数。
普通的多项式乘法是O(n^2)的,我们要枚举A(X)中的每一项,分别与B(x)中的每一项相乘,来得到一个新的多项式C(x)。
2.1 多项式的表示方法
(1)系数表示法:用幂次不定元及其系数表示的多项式,比如A(X)。
(2)点值表示法:任何一个n次多项式,都能由n+1个不同的点唯一确定,用这n+1个点表示的多项式称为多项式的点值表示。
2.2 DFT和IDFT的思路
已知A(X)和B(X)的系数表示,求C(X) = A(X)*B(X);
我们将A(X)与B(X)先转化为点值表示法,即:A(X) = { (p0,A(p0)), (p1,A(p1)),(p2,A(p2)),.....} 、B(X) = { (p0,B(p0)), (p1,B(p1)),(p2,B(p2)),..... };
则在点值表示法下:计算点值C(pi) = A(pi)*B(pi) 是 O(n) 的,这个过程叫做DFT。然后我们再通过IDFT将C(pi)的点值表示法转化为系数表示法,这个过程插值。但是遗憾的是:系数表示转化为点值表示是O(n^2) , 插值过程也是O(n^2) 。 在计算机中并不能提高效率。
(1)目的:我们想要利用DFT中点值表示法计算的O(n)复杂度,又想缩短转化过程中的时间复杂度。于是就有了FFT算法,其本质是利用分治的思想加快DFT和IDFT的计算过程,通过各种优化使得整个求解过程降为 O(nlogn)。
(2)过程:见下文。
(1)定义:设a,b为实数,,形如
(2)模长:从原点(0,0)到点(a,b)的距离,即
(3)幅角:假设以逆时针为正方向,从x轴正半轴到已知向量的转角的有向角叫做幅角;
(1)几何定义:复数相乘,模长相乘,幅角相加(所以说模长为1的复数相乘永远模长为1);
(2)代数定义:(a+bi)∗(c+di) = (ac−bd)+(bc+ad)i;
(1)基本概念
已知:;则满足这个方程的复数解z , 称为n次单位根,记为
;这样的的 n次根有n个:它们位于复平面的单位圆上,构成正n边形的顶点,其中一个顶点是1。
根据复数乘法的运算法则,其余n个复数为且
如八次单位根如下:
(2)基本性质(FFT就是利用这个性质简化运算)
(1)已知:一个n-1次多项式,可以被n个不同的点值唯一确定。
(2)所以在求点值过程中,我们必须选择n个x带入方程,来得到n个点值为该方程的点值表示。
(3)那么这n个自变量x怎么选择呢?前面我们讲了这么多,当然是代入n次单位根的n个值
(4)接下来是利用单位根的性质来简化运算,加速求值过程。
设所要求值多项式为:
令
令
则,那么:
(1)假设我们代入
(2)同理当代入另一半值时代入
综上所述:我们只需要n/2个值,就能求出A(x)的n个点值表示,可以看出来,我们将问题的规模缩小了一半!只要我们不断往下分治,最后到常数项返回,复杂度会缩短为O(nlogn)!
(1)有了最终多项式的点值表示法以后,我们要把点值表示法转化为系数表示法:
(2)假设方程为 W*a = A,则方程左侧同时乘以
即快速IDFT的过程是以所求点值表示的A(x)作为某方程T(x)系数,代入n个
注意:对于模长为1的复数,其倒数等同于其共轭复数。即(
已知n+1项n次多项式A(X)和m+1项m次多项式B(X),求C(X) = A(X)*B(X)。
则C(X)最多为n+m+1项n+m次多项式,我们需要将A(X)和B(X)均补齐到>=n+m+1的最小2幂次项limit,缺的幂次前面系数补0(为了分治过程中满足每次都能均分为奇偶分段),在输出时不输出多余项即可。
需要代入limit次单位根的limit个值,即和
。
即C(X) = A(X)*B(X)。
需要代入limit次单位根倒数的limit个值,即
,注意结果不要忘记 /n。
洛谷P3803 【模板】多项式乘法(FFT)
- #include<iostream>
- #include<bits/stdc++.h>
- #define PI acos(-1)
- using namespace std;
- const int maxn = 1000000 + 7;
- int n,m;
- complex<double> a[maxn*3],b[maxn*3];
- void FFT(int len,int type,complex<double> *c){
- if(len==1)return;
- complex<double> codd[len>>1];
- complex<double> ceven[len>>1];
- for(int i = 0;i<len;i+=2){//奇偶分离A(X) = A1(X) + x*A2(X)
- ceven[i>>1] = c[i];//A1(X)
- codd[i>>1] = c[i+1];//A2(X)
- }
- FFT(len>>1,type,ceven);//求子问题
- FFT(len>>1,type,codd);
- for(int i = 0;i<(len>>1);i++){//套公式
- complex<double> wn(cos(2.0*PI*i/len),type*sin(2.0*PI*i/len));
- c[i] = ceven[i] + wn*codd[i];
- c[i+(len>>1)] = ceven[i] - wn*codd[i];
- }
-
- }
-
- int main(){
- int x;
- int maxLen = 1;
- scanf("%d%d",&n,&m);
- while(maxLen<n+m+1)maxLen<<=1;//补项到最终结果最小的2的幂次项
- for(int i = 0;i<=n;i++){
- scanf("%d",&x);
- a[i].real(x);//n+1个A(X)系数a0......an
- }
- for(int i = 0;i<=m;i++){
- scanf("%d",&x);
- b[i].real(x);//m+1个B(X)系数b0....bm
- }
-
- FFT(maxLen,1,a);//DFT求点值(nlogn)
- FFT(maxLen,1,b);
-
- for(int i = 0;i<maxLen;i++){//求C(X)点值
- a[i]*=b[i];
- }
- FFT(maxLen,-1,a);//IDFT
- for(int i = 0;i<=n+m;i++){//只输出到n+m+1项
- if(i!=0)printf(" ");
- printf("%d",(int)(a[i].real()/maxLen+0.5));
- }
- printf("\n");
- return 0;
- }
首先找一下每个数字递归到最后所在的位置关系:
我们发现:每一个位置处最后的位置是该位置最初二进制的反转,所以我们怎么实现呢?在原序列中:
(1)若位置i为偶数:位置i的反转位置 = i/2的二进制左移一位
(2)若位置i为奇数:位置i的反转位置 = i/2的二进制左移一位后在最高位补1
所以我们用数组保存下每个原序列位置最后的位置:pos[i] = (pos[i>>1]>>1)|((i&1)<<(l-1)) 。交换完毕以后,这就相当于递归到的最后一层,我们需要不断向上合并得到最终答案。
注意:使用complex<double> Wn (cos(2.0*PI/L),type*sin(2.0*PI/L)); + complex<double>w(1,0); + w=w*Wn的方式来求
- #include <iostream>
- #include<bits/stdc++.h>
- using namespace std;
- const int maxn = 1000000 + 7;
- #define PI acos(-1)
- int n,m;
- complex<double> a[maxn*3],b[maxn*3];
- int pos[maxn*3];
- void FFT(complex<double>*A,int len,int type){
- for(int i = 0;i<len;i++){//把每个数放到最后的位置
- if(i<pos[i])swap(A[i],A[pos[i]]);//保证每对只交换一次
- }
- for(int L = 2;L<=len;L<<=1){//循环合并的区间长度
- int HLen = L/2;//区间的一半
- complex<double> Wn (cos(2.0*PI/L),type*sin(2.0*PI/L));
- for(int R = 0;R<len;R+=L){//每个小区间的起点
- complex<double>w(1,0);
- for(int k = 0;k<HLen;k++,w=w*Wn){//求该区间下的值
- complex<double> Buf = A[R+k];//蝴蝶操作,去掉odd和even数组,使变化原地进行
- A[R+k] = A[R+k] + w*A[R+k+HLen];
- A[R+k+HLen] = Buf - w*A[R+k+HLen];
- }
- }
- }
- }
- int main()
- {
- int x;pos[0] = 0;
- int maxLen = 1,l = 0;
- scanf("%d%d",&n,&m);
- while(maxLen<n+m+1){maxLen<<=1;l++;}
- for(int i = 0;i<=n;i++){
- scanf("%d",&x);a[i].real(x);
- }
- for(int i = 0;i<=m;i++){
- scanf("%d",&x);b[i].real(x);
- }
- for(int i = 0;i<maxLen;i++){//求最后交换的位置(奇数特殊处理)
- pos[i] = (pos[i>>1]>>1)|((i&1)<<(l-1));
- }
- FFT(a,maxLen,1);
- FFT(b,maxLen,1);
- for(int i = 0;i<maxLen;i++)a[i]*=b[i];
- FFT(a,maxLen,-1);
- for(int i = 0;i<n+m+1;i++){
- if(i!=0)printf(" ");
- printf("%d",(int)(a[i].real()/maxLen+0.5));
- }
- printf("\n");
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。