# 前言
这篇文章咕的很久,三角函数似乎没啥用。
# 三角函数
# 前前言
三角函数似乎没啥用。三角函数似乎没啥用。三角函数似乎没啥用。
# 单位圆
一个以原点为中心且半径为 1 的圆。
# 公式
cos(α−β)=cosαcosβ+sinαsinβ
cos(α+β)=cosαcosβ−sinαsinβ
sin(α+β)=sinαcosβ+cosαsinβ
sin(α−β)=sinαcosβ−cosαsinβ
作以 x 正半轴为始边,逆时针旋转 α,β,α−β 的终边与单位圆分别交于 A(cos(α),sin(α)),B(cos(β),sin(β)),C(cos(α−β),sin(α−β)), x 正半轴与单位圆交于 D(1,0), 有 ∣AB∣=∣CD∣。
(cosα−cosβ)2+(sinα−sinβ)2=(1−cos(α−β))2+sin2(α−β)
(cosα−cosβ)2+(sinα−sinβ)2(1−cos(α−β))2+sin2(α−β)=cos2α−2cosαcosβ+cos2β+sin2α−2sinαsinβ+sin2β=2−2cosαcosβ−2sinαsinβ=1−2cos(α−β)+cos2(α−β)+sin2(α−β)=2−2cos(α−β)
2−2cosαcosβ−2sinαsinβ=2−2cos(α−β)
cosαcosβ+sinαsinβ=cos(α−β)
cos(−β)=cosβ
sin(−β)=−sinβ
cos(α+β)=cosαcosβ−sinαsinβ
sin(α+β)=cos(2π−α−β)=cos((2π−α)−β)=cos(2π−α)cosβ+sin(2π−α)sinβ=sinαcosβ+cosαsinβ
sin(α−β)=sinαcos(−β)−cosαsin(−β)sinαcosβ−cosαsinβ
# 虚数
# 定义
i=−1
a+bi
# 公式
(a+bi)+(c+di)=(a+c)+(b+d)i
(a+bi)−(c+di)=(a−c)+(b−d)i
(a+bi)×(c+di)=ac+adi+bci+bd×(−1)2=(ac−bd)+(ad+bc)i
# 单位复根
# 坐标轴
x 代表实数 y 代表虚数。
# 定义
n 次单位复根是满足 ωnn=1 的复数 ωn,一共有 n 个。
如何理解?画一个单位圆,将其 n 等分,圆上 n 个点就是 n 次单位复根。
因此有:
ωn=cos(n2π)+sin(n2π)i
ωnk=cos(n2πk)+sin(n2πk)i
# 消去定理
ωnmkm=ωnk
ωnmkm=cos(nm2πkm)+sin(nm2πkm)i=cos(n2πk)+sin(n2πk)i=ωnk
# 消去定理的推论
ωnn/2=ω2=−1
# 折半定理
若 'n&1=0' 且 n>0,有:
(ωnk+n/2)2=(ωnk)2
(ωnk+n/2)2=(ωnkωnn/2)2=(ωnk×(−1))2=(ωnk)2
# 求和定理
对于任意整数 1≤n 和不能被 n 整除的非负整数 k,有
j=0∑n−1(ωnk)j=0
# 多项式的表达
# 点值表达
一个次数界为 n 的多项式 A(x) 的点值表达是一个由 n 个点对组成的集合:
{(x0,y0),(x1,y1),⋯,(xn−2,yn−2),(xn−1,yn−1)}
其中 xi 互不相同。
一个傻逼的性质, A(x)=B(x)+C(x) 时有:
A(x)={(x0,By0+Cy0),(x1,By1+Cy1),⋯,(xn−2,Byn−2+Cyn−2),(xn−1,Byn−1+Cyn−1)}
A(x)=B(x)×C(x) 时有:
A(x)={(x0,By0×Cy0),(x1,By1×Cy1),⋯,(xn−2,Byn−2×Cyn−2),(xn−1,Byn−1×Cyn−1)}
# 系数表达
一个次数界为 n 的多项式 A(x) 的系数表达是一个由 n 个系数组成的多项式:
a0x0+a1x1+a2x2+⋯+an−2xn−2+an−1xn−1
# 系数表达与点值表达的关系
对于任意一个由 n 个点对组成的点值表达都有唯一一个次数界为 n 的多项式与其对应。
有 yi=A(xi)。
把其写成矩阵形式:
⎣⎢⎢⎢⎢⎡1x0x021x1x12⋮⋮⋮1xn−1xn−12⋯x0n−1⋯x1n−1⋱⋮⋯xn−1n−1⎦⎥⎥⎥⎥⎤ ⎣⎢⎢⎢⎢⎡a0a1⋮an−1⎦⎥⎥⎥⎥⎤ = ⎣⎢⎢⎢⎢⎡y0y1⋮yn−1⎦⎥⎥⎥⎥⎤
范德蒙德矩阵在 xi 皆不同的情况下有逆矩阵,因此有点值表达,能够确定系数 ai。
# 快速傅里叶变换 & 快速傅里叶逆变换
规定 n 是 2 的 q 次幂。
已知
A(x)=a0x0+a1x1+a2x2+⋯+an−2xn−2+an−1xn−1
求
{(ωn,y0),(ωn1,y1),⋯,(ωnn−2,yn−2),(ωnn−1,yn−1)}
分治!!!
A0(x)=a0x0+a2x1+⋯+an−2xn/2−1
A1(x)=a1x0+a3x1+⋯+an−1xn/2−1
A2(x)=x
A(x)=A0(x2)+A2(x)A1(x2)
A0={(ωn/2,y0),(ωn/21,y1),⋯,(ωn/2n/2−2,yn/2−2),(ωn/2n/2−1,yn/2−1)}
A1={(ωn/2,y0),(ωn/21,y1),⋯,(ωn/2n/2−2,yn/2−2),(ωn/2n/2−1,yn/2−1)}
A2={(ωn,y0),(ωn1,y1),⋯,(ωnn−2,yn−2),(ωnn−1,yn−1)}
A(ωni)=A0(ωn2i)+ωniA1(ωn2i)
A(ωni)=A0(ωn/2i)+ωniA1(ωn/2i)
注意,ωn/2i 在 A0 和 A1 的点值表达中出现了!!!
巨丑的伪代码:
| FFT(a): |
| n=a.length |
| if n==1 |
| return a |
| w=e^{2 * pi * i / n} |
| W=1 |
| A0={a0,a2,...,a(n-2)} |
| A1={a1,a3,...,a(n-1)} |
| y0=FFT(A0) |
| y1=FFT(A1) |
| for k in range(n/2): |
| y[k]=y0[k]+W*y1[k] |
| y[k]=y0[k]-W*y1[k] |
| W*=w |
| return y |
这样写为什么是对的???
y0ky1kykA(ωnk)A(ωnk+n/2)=A0(ωn/2k)=A1(ωn/2k)=A(ωnk)=y0k+ωnky1k=A0(ωn/2k)+ωnkA1(ωn/2k)=A0((ωnk+n/2)2)+ωnk+n/2A1((ωnk+n/2)2)=A0(ωn2k)+ωnn/2ωnkA1(ωn2k)=A0(ωn/2k)+(−1)ωnkA1(ωn/2k)=A0(ωn/2k)−ωnkA1(ωn/2k)
已知
{(ωn,y0),(ωn1,y1),⋯,(ωnn−2,yn−2),(ωnn−1,yn−1)}
求
A(x)=a0x0+a1x1+a2x2+⋯+an−2xn−2+an−1xn−1
有
⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡1111111ωn1ωn2ωn3⋮ωnn−11ωn2ωn4ωn6⋮ωn2(n−1)1ωn3ωn6ωn9⋮ωn3(n−1)1ωn4ωn8ωn12⋮ωn4(n−1)⋯⋯⋯⋯⋱⋯1ωnn−1ωn2(n−1)ωn3(n−1)⋮ωn(n−1)(n−1)⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡a0a1a2a3⋮an−1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡y0y1y2y3⋮yn−1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
Vn=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡1111111ωn1ωn2ωn3⋮ωnn−11ωn2ωn4ωn6⋮ωn2(n−1)1ωn3ωn6ωn9⋮ωn3(n−1)1ωn4ωn8ωn12⋮ωn4(n−1)⋯⋯⋯⋯⋱⋯1ωnn−1ωn2(n−1)ωn3(n−1)⋮ωn(n−1)(n−1)⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
Vn−1=n1⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡1111111ωn−1ωn−2ωn−3⋮ωn−(n−1)1ωn−2ωn−4ωn−6⋮ωn−2(n−1)1ωn−3ωn−6ωn−9⋮ωn−3(n−1)1ωn−4ωn−8ωn−12⋮ωn−4(n−1)⋯⋯⋯⋯⋱⋯1ωn−(n−1)ωn−2(n−1)ωn−3(n−1)⋮ωn−(n−1)(n−1)⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤
Vn−1Vn=⎣⎢⎢⎢⎢⎢⎢⎡10000010⋮0001⋮0⋯⋯⋯⋱⋯000⋮1⎦⎥⎥⎥⎥⎥⎥⎤
[Vn−1Vn]i,j=k=0∑n−1(ωn−ki/n)(ωnkj)=k=0∑n−1ωnkj−ki/n
应用求和引理。
在看原来的柿子:
{(ωn,y0),(ωn1,y1),⋯,(ωnn−2,yn−2),(ωnn−1,yn−1)}
求
A(x)=a0x0+a1x1+a2x2+⋯+an−2xn−2+an−1xn−1
有
aj=n1k=0∑n−1ykωn−kj
稍微变换一下 FFT 的过程即可。
# 快速傅里叶变换 & 快速傅里叶逆变换的优化
改成迭代计算即可,因为在同一层的计算会用到相同的单位复根,可以减少常数。
简单的说,将序列位逆序置换,得到最低层的计算顺序,然后不断向上计算,调整位置(实际上在合并时就自动调整位置了)。
# 最终代码
| #include <bits/stdc++.h> |
| using namespace std; |
| const int N = 2e7+11; |
| const double pi = acos(-1); |
| struct Comp{ |
| double a, b; |
| Comp operator + (const Comp &oth)const{return (Comp){a+oth.a, b+oth.b};}; |
| Comp operator - (const Comp &oth)const{return (Comp){a-oth.a, b-oth.b};}; |
| Comp operator * (const Comp &oth)const{return (Comp){a*oth.a-b*oth.b, a*oth.b+b*oth.a};} |
| }f[N], g[N], w_i[N]; |
| int n, m, rev[N]; |
| void FFT(int len, Comp *a, bool type){ |
| for(int i = 0; i < len; i ++){ |
| rev[i] = (rev[i>>1]>>1) + (i%2?(len>>1):0); |
| if(rev[i] > i) swap(a[rev[i]], a[i]); |
| } |
| for(int d = 1; d < len; d<<=1){ |
| Comp w_n = (Comp){cos(pi/d), sin(pi/d)}; |
| if(type) w_n.b = -w_n.b; |
| w_i[0] = (Comp){1, 0}; |
| for(int i = 1; i < d; i ++) w_i[i] = w_i[i-1]*w_n; |
| for(int fir = 0; fir < len; fir += d<<1){ |
| int sec = fir+d; |
| for(int i = 0; i < d; i ++){ |
| Comp A0 = a[fir+i], A1 = w_i[i] * a[sec+i]; |
| a[fir+i] = A0 + A1, a[sec+i] = A0 - A1; |
| } |
| } |
| } |
| if(type) for(int i = 0; i < len; i ++) |
| a[i].a /= len, a[i].b /= len; |
| return; |
| } |
| int main(){ |
| scanf("%d %d", &n, &m); |
| for(int i = 0; i <= n; i ++) scanf("%lf", &f[i].a); |
| for(int i = 0; i <= m; i ++) scanf("%lf", &g[i].a); |
| int len = 1; while(len <= n+m) len <<= 1; cout << len << endl; |
| FFT(len, f, 0), FFT(len, g, 0); |
| for(int i = 0; i < len; i ++) f[i] = f[i] * g[i], printf("%.2lf ", f[i].a); |
| puts(""); |
| FFT(len, f, 1); |
| for(int i = 0; i <= n+m; i ++) |
| printf("%d ", (int)(f[i].a+0.5)); |
| return 0; |
| } |