dustland

dustball in dustland

傅里叶变换

傅里叶变换

一个欠了一年的帐,今天终于还上了

我根本不知道录音的基本原理

O(nlgn)内求解多项式乘法

算法导论上的傅里叶变换,是为了在O(nlgn)时间内,解决多项式乘法

如果直接让两个多项式卷积,那么朴素方法复杂度显然是O(n2)

如何降复杂度呢?使用点值计算

线性代数上可以证明,一个n-1次多项式,可以在其图像上使用n个点唯一确定(插值方法)

也就是说,一个多项式A(x)=j=0n1ajxj可以用n个点表示

那么两个均为n1次的多项式A(x),B(x),其乘积多项式就得是一个2n2次多项式,需要2n2个点唯一确定

显然从一个n1次多项式上取n-1个点已经足够唯一确定这个多项式了,再取其他点就是冗余信息,对确定表达式没有影响,因此可以在A(x),B(x)上各取2n-2个点,计算这2n-2个点的积,这就得出了积多项式的点值表示

其中第i个点C(xi,A(xi)×B(xi))纵坐标为两个多项式在x0处函数值的乘积

画在图上也就是:

普通乘法是我们之前使用的lowB方法,下面曲线救国nb,就求值和插值这两步是真nb,求值和插值为何是O(nlgn)呢?

image-20221219191232000

使用之前学过的方法,这两步都是O(n2)的,

求值时需要将x0,x1...以此带入A(x)一共n个自变量,每个自变量求值都是O(n),因此总共O(n2)

插值时需要使用拉格朗日插值法,也是O(n2)

拉格朗日插值公式: A(x)=k=0n1ykΠjk(xxj)Πjk(xkxj) 其中(x0,y0),(x1,y1),...,(xn1,yn1)是多项式A(x)的点值表示

最外层这个求和已经是O(n)

用二维数组M[k][j]=xkxj预处理分母,

分子预先先直接求出F(x)=Π(xxj)

那么对于一个给定的k,分母就是n个数的积,O(n)

分子就是F(x)/(xxj),O(n)

乘上最外圈的O(n)得到O(n2)

也就是说,之前使用的方法是O(n2)

但是使用快速傅里叶变换这种吊法,就能给他降到O(nlgn)

怎么降的?选点值的时候有讲究

任意n个不同的点就可以确定一个n-1次表达式.

如果选n个单位复根,然后对系数向量(a0,a1,...,an1)应用离散傅里叶变换DFT,就可以在O(nlgn)内完成求值,使用逆DFT变换就可以在O(nlgn)完成插值

单位复根儿

"n次单位复根"就是指满足ωn=1的负数ω,注意是这个ω,不带那个n次方,一定要注意上下角标

欧拉公式 eiπ+1=0 第k个n次单位复根是ωk=e2πikn

显然ωkn=(e2πikn)n=e2πik=((eiπ)2)k=((1)2)k=1k=1

n次单位复根儿恰好有n个,也就是k=0,1,2,...,n1

这n个画在复平面儿上是很整齐的,比如8次复根就长这样:

任何两个相邻的单位复根之间的角度都是固定的,360/n=360/8

image-20221219193558548

都落在复平面单位元上

显然8次复根只有8个,如果非要写第九个ω89,就和ω80重合了,

然后书上就说这是加法群(Z8,+),确实是,除了装了个B对于解决问题没有帮助,甚至没有学过抽代的就蒙蔽了

DFT

求n-1次多项式的点值表示时,就需要选取n个点(x0,x1,x,...,xn1),然后求出各自的函数值(y0,y1,...,yn1)

如果选取的n个点是n个富哥儿,那么此时有 yk=A(ωnk)=j=0n1ajωnjk 以此得到的y=(y0,y1,...,yn1)叫做系数向量a=(a0,a1,...,an1)的离散傅里叶变换,

记作y=DFTn(a)

笑死,这看上去不就是带入求值吗,选n个富哥有屁用啊?

如果用普通的带入求值,找n个富哥儿和找n个普通人儿没有区别.

富哥儿的作用不能浪费喽,他们和普通值又有啥区别呢?

这就是单位复根儿的几个性质

这几个性质在计算时会体现群论的对称性,这意味着,这n个富哥确实能够比n个普通值携带更多的信息

image-20221219195023551

FFT

fast Fourier transform,快速傅里叶变换

O(nlgn)内求解DFT的算法

也就是说,DFT并不是一个求解方法,只是娶了n个富哥作为点值表示

仍然可以使用带入求值这种O(n2)的lowB算法求解

而FFT就是一种吊法

他这样变形:

首先n向上取整到最近的2的幂次,这样做是为了保证能够一直二分直到剩1项,反正多取点不会降低精度

后面的n默认就认为是2的幂次了 A(x)=a0+a1x+a2x2+...+an1xn1=(a0+a2x2+a4x4+...+an2xn2)+(a1x+a3x3+a5x5+...+an1xn1)=A[0](x2)+xA[1](x2) 也就是说将A(x)按照偶数项和奇数项分开,其中奇数项都提出一个x来,这样子多项式中的x都是偶数,可以提取x2作为自变量,于是得到 {A[0](x)=a0+a2x+a4x2+...+an2xn21A[1](x)=a1+a3x+a5x2+...+an1xn21 时刻不要忘记在求什么,

我们现在已知的是a=(a0,a1,a2,...,an1),x=(ωn0,ωn1,...,ωnn1)

要求的是y=DFTn(a)

现在可以这样算了: A(xk)=A(ωnk)=A[0](ωn2k)+ωnkA[1](ωn2k) 注意到我们不容易计算A(x)是因为,它的项太多了,0次项,1次项等等一直到n-1次项,共n项

而现在对于一个A[0](x)他只有n2项了,工作量直接下降到一半

以此递归,总会有个时候,A(x)只有一项,也就是0次项,也就是那个常数项,算也不用算直接返回了,和x没有关系了,和富哥没关系了

写出伪代码:

注意FFT函数的输入是目标多项式的系数向量,不是富哥,因为富哥是作为常数使用的,不需要参数传递

FFT函数输出的是y=DFT(a)也就是和将n个富哥直接带入多项式求值得到的n个值相同,但是算法更好

一定注意函数不是y=A(x),而是y=FFT(a)

一定要清楚函数在干什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vector<int> FFT(vector<int> a){//a是系数,返回y向量
n=a.length();
if(1==n){//递归出口条件,只剩一项时,也就是那个常数项,直接返回此时的a,实际上是a[0]作为y[0]返回了y
return a;
};
Complex omega_n=e^(2*pi*i/n);
vector<int> a0,a1;
for(int i=0;i<n;++i){
if(i%2==0)a0.push_back(a[i]);
else a1.push_back(a[i]);
}
int y0=FFT(a0);
int y1=FFT(a1);
vector<int> y(n);
for(int i=0;i<n/2;i++){//一定注意此处上界是n/2
y[i] =y0[i]+pow(omega_n,i)*y1[i];//容易理解
y[n/2+i]=y0[i]-pow(omega_n,i)*y1[i];//想对困难
}
return y;
}

这里面有一个y[n/2+i]=y0[i]-pow(omega_n,i)*y1[i],怎么得到的呢? y[n2+i]=A(ωnn2+i)=A[0](ωnn+2i)+ωnn2+iA[1](ωnn+2i)=A[0](ωn2i)+ωnn2+iA[1](ωn2i)=A[0](ωn2i)ωniA[1](ωn2i)=y0[i]ωniy1[i] 这就体现出富哥和普通点的区别了,富哥的变换很灵活

从上面的伪代码可以看出,最底层的递归和自变量无关.

加速发生在这里,将工程量直接缩减到一半

1
2
3
4
for(int i=0;i<n/2;i++){//一定注意此处上界是n/2
y[i] =y0[i]+pow(omega_n,i)*y1[i];//容易理解
y[n/2+i]=y0[i]-pow(omega_n,i)*y1[i];//想对困难
}

每次递归都将工程量缩减一半,因此总工程量直接对n取对数 T(n)=2T(n2)+Θ(n)=Θ(nlgn)

好了到此FFT的思路有了,求值结束,也就是我们求得了y=DFT(a)

举个例子,A(x)=1+2x+x2+x3,正好有22=4项,n=4

4次单位复根ω4k=e2πik4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FFT(<1,2,1,1>){
n=<1,2,1,1>.length()=4;
a0=<1,1>;
a1=<2,1>;
w1;
y0=FFT(<1,1>){
n=<1,1>.length()=2;
a0=<1>;
a1=<1>;
y0=FFT(<1>)=<1>;
y1=FFT(<1>)=<1>;
y[0]=y[0]+w1
}
}

下面考虑如何从点值形式倒回去,也就是如何在O(nlgn)实现插值

现在已知的是yx={ωn0,ωn1,...,ωnn1},求系数向量a

显然可以待定系数法设n个系数,然后带入自变量和因变量,解一元n次方程组.

高斯消元法是O(n2)的lowB算法,要用吊算法

写成矩阵形式

image-20221219211357315

也就是说,一眼顶针,鉴定为范德蒙矩阵,必然可逆且逆矩阵唯一

现在要用尽可能快的方法求解这个范德蒙矩阵V的逆矩阵,这个求解方法就是算法关键

这有个定理Vn1[j,k]=ωnkjn

怎么得出来的,我线性代数忘了,不会解,但是VV1算一下确实得到E,满足互逆矩阵的定义

由于a=V1y

aj就等于V1的第j行与y的乘积,也就是说 aj=k=0n1Vn1[j,k]×yk=k=0n1ωnkjn×yk=1nk=0n1ωnkj×yk 求解一个aj,是O(n)复杂度的,

共有n个aj,总共就是O(n2)复杂度的,怎么降到O(nlgn)呢?

回忆正向的FFT是干啥的来着

是求解y=DFTn(a)问题的

其中yj=k=0n1ajωnjk

类比一下 naj=k=0n1ykωnjkyj=k=0n1akωnjky=na,

a=y

令单位富哥变成单位负哥,就可以直接带入FFT函数求解了y

解出来都除以n就得到了a

因此又在O(nlgn)内解决了

回到多项式求积

卷积定理:

对于两个长度为n的向量a,b ab=DFT2n1(DFT2n(a)·DFT2n(b)) 将多项式A,B只保留系数看成向量a,b,此时ab就是积多项式AB的系数向量