一个超赞的博客
快速傅里叶变换
一个写的不太好的博客
说明
在oi里的大概用途:在O(nlog2n) 的时间复杂度内运算两个有n个项的多项式的乘积
C(x)=j=0∑2n−1i=0∑jaibj−ixj
多项式的乘积,x是不重要的。所以本质上解决的问题是:
ck=i=0∑kai×bk−i
也即卷积
原理
如果由系数表示直接到系数表示,复杂度是O(n2)的。然而,fft通过三步转化降低了复杂度:
- 系数表示 --> 点值表示 (dft/傅里叶变换)
- 点值乘法运算
- 点值表示 --> 系数表示 (idft/傅里叶逆变换/插值)
先来看第一步
第一步其实就是分别找n个合适的值带入A(x),B(x)两个函数,使得这两个函数由系数表示法转化为点值表示法
正常情况下,这个过程是O(n2)的,但是利用单位根优化可以做到O(nlogn)
前置内容:单位根
在复数系内,以原点为起点,以单位圆的n等分点为终点,做n个向量,设最小辐角对应的向量为ωn,其余n−1个复数表示为ωn0,ωn2,…,ωnn
则有:
- 周期性:
ωni=(ωn)i
- 消去引理:
ω2n2k=ωnk
- 折半引理:
ωnk+2n=−ωnk
就不证明了,可见引用的博客
以下把n视作2的整数次幂,如果不是,就当作系数是0
设A(x)=A0(x)+A1(x),其中:
A(x)=a0+a1x+a2x2+⋯+an−1xn−1
A0(x)=a0+a2x+⋯+an−2xn/2−1
A1(x)=a1+a3x+⋯+an−1xn/2−1
则有:
A(x)=A0(x2)+xA1(x2)
现在我们有n个复数了,我们把前n/2−1个带入(0≤k<n/2)得到:
A(ωnk)=A0(ωn/2k)+ωnkA1(ωn/2k)
把n/2到n−1个带入(n/2≤k′<n,k=k′−n/2)得到:
A(ωnk+n/2)=A0(ωn/2k)−ωnkA1(ωn/2k)
因为A的前半段与后半段函数值有简单关系,所以只要计算A0,A1的所有函数值就可以直接得到A的所有函数值
第二步很简单,只需要把对应函数值乘起来就好
第三步是第一步的逆过程,不想证明了,代码不需要写两份。
代码
修修滴代码,大大滴智慧~
#include <bits/stdc++.h>
using namespace std;
const int N=4e6+5;
const double PI=acos(-1);
char s[N];
complex<double>f[N],g[N];
void FFT(complex <double>*a,int n,int op){
if(!n) return;
complex<double> a0[n],a1[n];
for(int i=0;i<n;i++)
a0[i]=a[i<<1],a1[i]=a[i<<1|1];
FFT(a0,n>>1,op); FFT(a1,n>>1,op);
complex<double> W(cos(PI/n),sin(PI/n)*op),w(1,0);
for(int i=0;i<n;i++,w*=W)
a[i]=a0[i]+w*a1[i],a[i+n]=a0[i]-w*a1[i];
}
int main(){
for(m+=n,n=1;n<=m;n<<=1);
FFT(f,n>>1,1); FFT(g,n>>1,1);
for(int i=0;i<n;i++) f[i]*=g[i];
FFT(f,n>>1,-1);
for(int i=0;i<=m;i++)
printf("%.0lf ",fabs(f[i].real())/n);
return 0;
}
例题
P3338 力
给出 n 个数 q1,q2,…qn,定义
Ej = i=1∑j−1(i−j)2qi − i=j+1∑n(i−j)2qi
对 1≤i≤n,求 Ei 的值。
推式子可以得到:
Ei=j=0∑iqj×fi−j−j=0∑n−iqi+j×fj
其中fi=i21
令qi′=qn−i (逆序)
E就可以变成卷积形式了:
Ei=j=0∑iqj×fi−j−j=0∑n−iqn−i−j′×fj
经验总结:
- 可以通过设置数组的方法把式子变优雅
- 可以通过改变数组中元素顺序的方法把式子变优雅
- sigma的起始要是0,才优雅
P3723 礼物
有两个数字环,现在把它们按任意方法对齐,把它们的对应数字做差得到一个数列,可以在这个数列的每个数上加一个同样的值,最后权值是这个数列中所有数字的和,问这个和的最小值
设已经按某种方法对其,则最终答案为:
i=1∑n(ai+bi+c)2=∑(ai2)+∑(bi2)+nc2+2c(∑ai−∑bi)−∑aibi
其中要让nc2+2c(∑ai−∑bi)最小可以直接公式计算,因此改变的东西只有∑aibi,这个东西就很卷积:
化成卷积的方法:把a数组倒序后倍长,a与b卷积后得到数组的n+1到2n位就是可能的答案。
经验总结
- 多项式项数为定值,可以利用系数为0的特点搞
注意点
- 数组大小
- n/m不要搞混(尤其是最后/m的操作)
81dacd9d-fb4e-4bfd-8dbc-dd652c82d75a