FFT
说在前面
写了一上午,然后浏览器卡了,触发了自动刷新功能,87%的都没有了。
然后一怒之下删掉了。
这样的事情居然发生了两次,这已经是第三稿了。
从多项式说起
多项式的表示法
系数表示法
f(x)
=
a
0
x
0
+
a
1
x
1
+
a
2
x
2
+
⋯
+
a
n
x
n
\large\operatorname{f(x)}=a_0x^0+a_1x^1+a_2x^2+\cdots+a_nx^n
f(x)=a0x0+a1x1+a2x2+⋯+anxn
这是很常见的表示法。
这个多项式的最高次项是xn,所以称它为n次多项式。
引入一个概念叫次数界,事实上对于上述多项式,只要是大于n的整数,都是次数界。
一般我们用次数加一来作为次数界,所以当我们说一个多项式的次数界为k时,则其次数为k-1。
点值表示法
我们知道两点确定一条直线,三点可解抛物线或圆的方程。本质上就是通过函数上的若干个点解若干个方程求出上述的一系列系数 a 1 , a 2 , a 3 , ⋯ , a n a_1,a_2,a_3,\cdots,a_n a1,a2,a3,⋯,an。
显然一个n次方程需要n+1个点,(因为有n+1个未知数要求)。
多项式的运算法则及朴素算法时间复杂度分析
不妨设有以下两个多项式:
f ( x ) = a 0 x 0 + a 1 x 1 + a 2 x 2 + ⋯ + a n x n F 1 ( x 1 , f ( x 1 ) ) , F 2 ( x 2 , f ( x 2 ) ) , ⋯ , F n + 1 ( x n + 1 , f ( x n + 1 ) ) g ( x ) = b 0 x 0 + b 1 x 1 + b 2 x 2 + ⋯ + b n x n G 1 ( x 1 , g ( x 1 ) ) , G 2 ( x 2 , g ( x 2 ) ) , ⋯ , G n + 1 ( x n + 1 , g ( x n + 1 ) ) 当 然 啦 这 x 1 , x 2 , ⋯ , x n + 1 默 认 为 同 一 个 数 列 啦 。 \large f(x)=a_0x^0+a_1x^1+a_2x^2+\cdots+a_nx^n\\ F_1(x_1,f(x_1)),F_2(x_2,f(x_2)),\cdots,F_{n+1}(x_{n+1},f(x_{n+1}))\\ g(x)=b_0x^0+b_1x^1+b_2x^2+\cdots+b_nx^n\\ G_1(x_1,g(x_1)),G_2(x_2,g(x_2)),\cdots,G_{n+1}(x_{n+1},g(x_{n+1}))\\ \small 当然啦这x_1,x_2,\cdots,x_{n+1}默认为同一个数列啦。 f(x)=a0x0+a1x1+a2x2+⋯+anxnF1(x1,f(x1)),F2(x2,f(x2)),⋯,Fn+1(xn+1,f(xn+1))g(x)=b0x0+b1x1+b2x2+⋯+bnxnG1(x1,g(x1)),G2(x2,g(x2)),⋯,Gn+1(xn+1,g(xn+1))当然啦这x1,x2,⋯,xn+1默认为同一个数列啦。
然后把它们进行加加减减操作,不妨记结果为c(i)吧。
FFT的核心操作就是计算
c
(
i
)
=
f
(
i
)
∗
g
(
i
)
c(i)=f(i)*g(i)
c(i)=f(i)∗g(i)。
操作 | 系数表达 | 点值表达 |
---|---|---|
f ( x ) ± g ( x ) f(x)\pm g(x) f(x)±g(x) | c ( i ) = ( a 0 ± b ) x 0 + ( a 1 ± b 1 ) x 1 + ( a 2 ± b 2 ) x 2 + ⋯ + ( a n ± b n ) x n c(i)=(a_0\pm b)x^0+(a_1\pm b_1)x^1+(a_2\pm b_2)x^2+\cdots+(a_n\pm b_n)x^n c(i)=(a0±b)x0+(a1±b1)x1+(a2±b2)x2+⋯+(an±bn)xn | C 1 ( x 1 , f ( x 1 ) ± g ( x 1 ) ) , C 2 ( x 2 , f ( x 2 ) ± g ( x 2 ) ) , ⋯ , C n + 1 ( x n + 1 , f ( x n + 1 ) ± g ( x n + 1 ) ) C_1(x_1,f(x_1)\pm g(x_1)),C_2(x_2,f(x_2)\pm g(x_2)),\cdots,C_{n+1}(x_{n+1},f(x_{n+1})\pm g(x_{n+1})) C1(x1,f(x1)±g(x1)),C2(x2,f(x2)±g(x2)),⋯,Cn+1(xn+1,f(xn+1)±g(xn+1)) |
f ( x ) ∗ g ( x ) \large f(x)*g(x) f(x)∗g(x) | c ( i ) = ∑ 1 ≤ i ≤ n ∑ 1 ≤ j ≤ i a j ∗ b k − j + 1 ∗ x i \large c(i)=\sum_{1\le i\le n}\sum_{1\le j\le i}a_j*b_{k-j+1}*x^i c(i)=1≤i≤n∑1≤j≤i∑aj∗bk−j+1∗xi | C 1 ( x 1 , f ( x 1 ) ∗ g ( x 1 ) ) , C 2 ( x 2 , f ( x 2 ) ∗ g ( x 2 ) ) , ⋯ , C 2 n ( x 2 n , f ( x 2 n ) ∗ g ( x 2 n ) ) 当 然 事 先 需 要 先 求 2 n 个 点 C_1(x_1,f(x_1)*g(x_1)),C_2(x_2,f(x_2)*g(x_2)),\cdots,C_{2n}(x_{2n},f(x_{2n})*g(x_{2n}))\\当然事先需要先求2n个点 C1(x1,f(x1)∗g(x1)),C2(x2,f(x2)∗g(x2)),⋯,C2n(x2n,f(x2n)∗g(x2n))当然事先需要先求2n个点 |
时间复杂度比较容易看出:
操作 | 时间复杂度 |
---|---|
加减法-系数表示法 | Θ(n) \text{Θ(n)} Θ(n) |
加减法-点值表示法 | Θ(n) \text{Θ(n)} Θ(n) |
乘法-系数表示法 | Θ ( n 2 ) \Theta(n^2) Θ(n2) |
乘法-点值表示法 | Θ(n) \text{Θ(n)} Θ(n) |
所以在计算多项式乘法
[
c
(
i
)
=
f
(
i
)
∗
g
(
i
)
]
\left[c(i)=f(i)*g(i)\right]
[c(i)=f(i)∗g(i)]的时候,我们更倾向于用点值表示法——
可是可是,f(i)和g(i)是用的系数表示法给出的哦!
生活中我们一般用的都是点值表示法耶!
所以说——
“你事先要求出2n个点耶!这样的时间复杂度的就是n2级别的啦!”
(
2
n
个
x
值
,
计
算
f
(
n
)
需
要
Θ
(
n
)
的
时
间
复
杂
度
)
(1*)
\left(2n个x值,计算f(n)需要\Theta(n)的时间复杂度\right) \tag{1*}
(2n个x值,计算f(n)需要Θ(n)的时间复杂度)(1*)
“还有,你
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)求出
c
(
i
)
c(i)
c(i)的点值表达法之后,怎么转回系数表达法呢?难道要
Θ
(
n
3
)
\Theta(n^3)
Θ(n3)的高斯消元法吗?”
以上!
这就是FFT的功能!
把一个多项式从系数表达法和点值表达法高效互化!
注释1*:
使用秦九昭算法可以规避使用快速幂沿着公式硬算,真正的实现
Θ
(
n
)
\Theta(n)
Θ(n)。
具体如下:
f
(
x
)
=
a
0
x
0
+
a
1
x
1
+
a
2
x
2
+
⋯
+
a
n
x
n
=
a
0
+
x
(
a
1
+
x
(
a
2
+
x
(
a
3
+
x
(
⋯
x
(
a
n
−
1
+
x
a
n
)
)
⋯
)
)
)
f(x)=a_0x^0+a_1x^1+a_2x^2+\cdots+a_nx^n= a_0+x\left(a_1+x\left(a_2+x\left(a_3+x\left(\cdots x\left(a_{n−1}+xa_n\right)\right)\cdots\right)\right)\right)
f(x)=a0x0+a1x1+a2x2+⋯+anxn=a0+x(a1+x(a2+x(a3+x(⋯x(an−1+xan))⋯)))
求值和插值
从系数表示法转换为点值表示法,称为求值。
从点值表示法转换为系数表示法,称为插值。
秦九韶算法就是一种优异的的求值方法。