前言
快速傅立葉變換 (Fast Fourier Transform)
,即利用計算機計算離散傅立葉變換(DFT)的高效、快速計算方法的統稱,簡稱FFT,於1965年由J。W。庫利和T。W。圖基提出。
FFT用於用來
加速多項式乘法
,可以以
的時間複雜度解決樸素高精度
複雜度能夠解決的多項式乘法。
複數
係數與點值表示法
對於任意的
項
次整係數多項式
,顯而易見我們可以將其表示為
的形式。
我們同樣可以用每一項的係數表示
,即:
這就是
係數表示法
。
當我們將多項式
看作一個
函式
時,如果我們將
個不同的數
帶入,就能得到
個不同的點。
這
個不同的點同樣可以看作是
個不同的一元
次方程聯立,這
個點或者方程也能反過來
唯一確定
。
所以
也可以表示為
這就是
點值表示法
。
我們假設兩個多項式
分別為:
其乘積為:
顯然點值表示法完成多項式乘法的時間複雜度僅有
,所以剩下的問題就是如何將係數表示法轉為點值表示法了。
單位根
以單位圓點為起點,單位圓的
等分點為終點,在單位圓上可以得到
個複數,設幅角為正且最小的複數為
,稱為
次單位根
。
由於複數乘法
模數相乘
,
輻角相加
的性質,編號為
的點
。
由
尤拉公式
可得
顯然
。
單位根同樣具有以下性質:
上述性質從幾何的角度看就很容易理解。
快速傅立葉變換
我們假設多項式
,那麼我們可以按
下標的奇偶性將
分為兩半。
也就是
我們令
顯然
。
帶入
可得
帶入
可得
我們發現上面的兩個式子只有一個常數項不同,所以當我們求出第一個式子的值後可以以
的時間複雜度求出第二個式子的值。
所以我們就成功將原來的問題縮小到了之前的一半,而縮小後的問題還能繼續縮小。
所以顯然FFT的時間複雜度即為
。
快速傅立葉逆變換
我們把兩個多項式相乘(或者說求
卷積
),我們跑完FFT並相乘後我們就得到了積的多項式的
點值表示
,現在我們需要將點值表示轉為
係數表示
,這個轉換的過程我們稱為
離散傅立葉逆變換
(
IDFT
)。
如果我們用矩陣將DFT的過程封裝,那麼DFT就相當於:
其中
意義為多項式的係數表示法,
意義為多項式的點值表示法,
即為
。
注意到
於是在
時
否則
其中
為
次單位根。
受
次單位根這一特性的啟發,注意到
在其形式上與矩陣乘法的關係,可以發現
這表明
即為
的逆矩陣。
由上文標紅部分
所以
將一個多項式在分治的過程中乘上的單位根變為其共軛複數,分治完的每一項除以
即可得到原多項式的每一項係數
。
具體實現(遞迴版)
//LuoguP3803
#include
const
int
NR
=
1
<<
22
;
const
double
eps
=
1e-6
,
pi
=
acos
(
-
1。0
);
using
namespace
std
;
complex
<
double
>
a
[
NR
],
b
[
NR
];
//complex為C++自帶虛數
int
n
,
m
;
void
FFT
(
complex
<
double
>
*
a
,
int
n
,
int
inv
)
//inv為虛部符號,inv為1時FFT,inv為-1時IFFT
{
if
(
n
==
1
)
//如果需要轉換的只有一項就直接返回
return
;
int
mid
=
n
/
2
;
complex
<
double
>
A1
[
mid
+
1
],
A2
[
mid
+
1
];
for
(
int
i
=
0
;
i
<=
n
;
i
+=
2
)
//拆分多項式
{
A1
[
i
/
2
]
=
a
[
i
],
A2
[
i
/
2
]
=
a
[
i
|
1
];
}
FFT
(
A1
,
mid
,
inv
);
//遞迴分治
FFT
(
A2
,
mid
,
inv
);
complex
<
double
>
w0
(
1
,
0
),
wn
(
cos
(
2
*
pi
/
n
),
inv
*
sin
(
2
*
pi
/
n
));
//單位根
for
(
int
i
=
0
;
i
<
mid
;
++
i
,
w0
*=
wn
)
//合併多項式
{
a
[
i
]
=
A1
[
i
]
+
w0
*
A2
[
i
];
a
[
i
+
n
/
2
]
=
A1
[
i
]
-
w0
*
A2
[
i
];
}
}
int
main
()
{
scanf
(
“%d%d”
,
&
n
,
&
m
);
for
(
int
i
=
0
;
i
<=
n
;
++
i
)
//輸入第一個多項式
{
double
x
;
scanf
(
“%lf”
,
&
x
);
a
[
i
]。
real
(
x
);
//complex型別變數。real(x)意味著將實數部賦為x,real()返回實數部值
}
for
(
int
i
=
0
;
i
<=
m
;
++
i
)
//輸入第二個多項式
{
double
x
;
scanf
(
“%lf”
,
&
x
);
b
[
i
]。
real
(
x
);
}
int
len
=
1
<<
max
((
int
)
ceil
(
log2
(
n
+
m
)),
1
);
//由於FFT需要項數為2的整數次方倍,所以多項式次數len為第一個大於n+m的二的正整數次方
FFT
(
a
,
len
,
1
);
//係數表達轉點值表達
FFT
(
b
,
len
,
1
);
for
(
int
i
=
0
;
i
<=
len
;
++
i
)
a
[
i
]
=
a
[
i
]
*
b
[
i
];
//O(n)乘法
FFT
(
a
,
len
,
-
1
);
//點值表達轉系數表達
for
(
int
i
=
0
;
i
<=
n
+
m
;
++
i
)
//輸出
printf
(
“%。0f ”
,
a
[
i
]。
real
()
/
len
+
eps
);
//記得要除n,eps為解決掉精度問題
return
0
;
}
FFT的最佳化
快速數論變換(NTT)
關於某沒有精度損失還可以取模的比FFT快的小常數多項式乘法。jpg
例題