Skip to content →

乗算:Karatsuba, Toom-Cook, FFT

多項式同士の乗算を高速に計算するアルゴリズムである、Karatsuba乗算、Toom-Cook法、FFT を紹介します(正確には FFT を途中で使うアルゴリズム)。

解くのは次の問題です:

問題:
\(\mathbb{Z}/p\mathbb{Z}\) 係数(\(p\) : 素数)の多項式 $$f(x) = \sum_{i=0}^n a_ix^i,$$$$g(x) = \sum_{i=0}^n b_ix^i$$ が与えられます。二つの積 \(f(x)g(x)\) を求めてください。

Karatsuba乗算は \(O(n^{\log_2(3)})\), Toom-Cook法は \(O(n^{1+\varepsilon})\), FFTは \(O(n\log(n))\) で \(f(x)g(x)\) が求まります。

Karatsuba乗算

\(f(x)=a_0+a_1x,g(x)=b_1+b_1x\) の積を計算するには、素朴には \(a_0b_0,a_0b_1, a_1b_0,a_1b_1\) の4つの積を計算する必要があります。しかし、次のように変形すると、$$\begin{align} &(a_0+a_1x)(b_0+b_1x) \\ =& a_0b_0 + (a_0b_1 + a_1b_0)x + a_1b_2x^2 \\ =& a_0b_0 + \{(a_0 + a_1)(b_0 + b_1) – a_0b_0- a_1b_1\} x + a_1b_1x^2\end{align}$$\(a_0b_0,(a_0+a_1)(b_0+b_1),a_1b_1\) の3つで済みます。\(f(x),g(x)\) が \(n\) 次式のときは \( n/2 \) 次式に分けて再帰的に掛け算します。この計算方法をKaratsuba乗算といいます。\(n\) 次式の掛け算の計算量を \(T(n)\) とすると、$$T(n)=3T(n/2)+O(n)$$です。\(3T(n/2)\) が3つの乗算、\(O(n)\) が1つの加算です。\(n^c = 3(n/2)^c \) を解くと \(c=\log_2(3) > 1\) なので、$$T(n) = O(n^{\log_2(3)}) \subset O(n^{1.59})$$ を得ます。愚直 \(O(n^2)\) より高速になりました! Karatsubaは、Kolmogorovが「乗算は \(\Theta(n^2)\) が最適だろう」と予想したのを聞いて、一週間でこのアルゴリズムを思いついたと振り返っています。

このアルゴリズムは2次式の多点評価、各点積、多項式補間で理解できます。
\(h(x)=c_0+c_1x+c_2x^2\) は多点評価 \(h(0),h(1),h(\infty)\) から $$h(x)=h(0)+\{h(1)-h(0)-h(\infty)\}x+h(\infty)x^2$$と多項式補間できます(\(h(\infty)\) は \(x^2\) の係数)。\(h(x)=f(x)g(x)\) とすると、\(x=0,1,\infty\) での多点評価は、\(f(0),f(1),f(\infty),g(0),g(1),g(\infty)\) を個別に求めてから、それぞれ掛ければよいです(\(f(\infty), g(\infty)\) は \(x\) の係数)。これは、Karatsuba乗算そのものです。

ところで、”2”次式の多点評価、各点積、多項式補間が最適なのでしょうか。3次式、4次式、5次式、…etcだと、どうなるのでしょうか(→Toom-Cook法, FFT)。また、評価点は \(x=0,1,\infty\) が最良でしょうか(→FFT)。

Toom-Cook法

Karatsuba法は2次式の多点評価、各点積、多項式補間でした。次数を上げて同じことをしたのがToom-Cook法です。

\(2d\) 次式で同じことをします。\(x=x_0,x_1,\ldots,x_d\) での多点評価は次の行列を、多項式の係数からなるベクトルに掛けることに相当します。
$$\begin{pmatrix}
x_0^0 & x_0^1 & x_0^2 & \cdots & x_0^{2d} \\
x_1^0 & x_1^1 & x_1^2 & \cdots & x_1^{2d} \\
x_2^0 & x_2^1 & x_2^2 & \cdots & x_2^{2d} \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
x_{2d}^0 & x_{2d}^1 & x_{2d}^{2} & \cdots & x_{2d}^{2d}
\end{pmatrix} \begin{pmatrix}
h_0 \\ h_1 \\ h_2 \\ \vdots \\ h_{2d}
\end{pmatrix} = \begin{pmatrix}h(x_0) \\ h(x_1) \\ h(x_2) \\ \vdots \\ h(x_{2d})\end{pmatrix}$$この行列はヴァンデルモンド行列と呼ばれ、\(2d+1\) 点が全て異なるならば逆行列が存在します。多項式補間は逆行列を掛けることに相当します。\(d\) を定数とすると、行列を掛けることや逆行列を求めることは定数時間でできます。多点評価、各点積、多項式補間を \(2d\) 次式ですることにします。

\(n\) 次式 \(f(x)\) と \(g(x)\) の積を求めるアルゴリズムは次のようになります。

  1. \(n\) 次式 \(f(x)\) を \(d+1\) 個の \(n/(d+1)\) 次以下の式 \(f(x)=f_0(x)+f_1(x)x^{\lceil n/(d+1) \rceil}+f_2(x)x^{2 \lceil n/(d+1) \rceil}+\cdots+f_{d}(x)x^{d\lceil n/(d+1) \rceil}\) に分けます。
  2. \(n\) 次式 \(g(x)\) を \(d+1\) 個の \(n/(d+1)\) 次以下の式 \(g(x)=g_0(x)+g_1(x)x^{\lceil n/(d+1) \rceil}+g_2(x)x^{2\lceil n/(d+1) \rceil}+\cdots+g_{d}(x)x^{d\lceil n/(d+1) \rceil}\) に分けます。
  3. 先の行列を用いて、\(f(x)\) と \(g(x)\) の積を \(2d\) 次式の多点評価、各点積、多項式補間に帰着します。各点積では、\(2d+1\) 個の高々 \(n/(d+1)\) 次の多項式の積が計算されています。再帰的にこの各点の多項式の積を求めます。

\(n\) 次式の乗算をこのアルゴリズムに従って行うときの計算量を \(T(n)\) とすると$$T(n)=(2d+1)T(n/(d+1))+O(n)$$が成り立ち、解くと \(T(n)=O(n^{\log_{d+1}(2d+1)})\) です。\(d\) について、\(\log_{d+1}(2d+1)\) は単調減少で、\(d\to\infty\) のとき、\(\log_{d+1}(2d+1)\to 1\) です。つまり、任意の \(\varepsilon>0\) について、\(O(n^{1+\varepsilon})\) で多項式乗算できるアルゴリズムが手に入りました。

FFT

FFTでは多点評価する点をうまく選ぶことで、ヴァンデルモンド行列とその逆行列を高速で掛け算できます。

\(0\) で高次の項を埋めて、オーダーを変えずに掛け算する多項式の次数が \(2^m\) で表せると仮定できます。\(1\) の原始 \(2^{m+1}\) 乗根を \(\zeta\) と置いて、多点評価する \(n=2^{m+1}\) 点を \(x=\zeta^0,\zeta^1,\ldots,\zeta^{n-1}\) とします。

\(\sum_{i=0}^{n-1} \zeta^{ai} \zeta^{-bi} = n\delta_{a,b}\) なので、ヴァンデルモンド行列 \((\zeta^{ij})\) の逆行列は \((\frac{1}{n}(\zeta^{-1})^{ij})\) です。 多項式補間をするには、\(\zeta\) を \(\zeta^{-1}\) で置き換えて多点評価をしてから、\(n\) で割ればよいです。従って、ヴァンデルモンド行列を高速に掛けられることを示せば十分です。

因数定理より点 \(a\) での評価は \(f(a) = (f(x) \bmod (x-a))\) と書けます。つまり、\(i=0,1,\ldots,n-1\) に対し $$f(\zeta^i) = f(x) \bmod (x-\zeta^i)$$ です。mod を取る多項式を $$x^n-1=(x-\zeta^0)(x-\zeta^1)\cdots(x-\zeta^{n-1})$$ から半分ずつ因数を落としていくことで、高速化します。

多点評価する点のべき指数を2進表記したときの \(m-k+1\) 桁目の偶奇で分けると、
$$\begin{align}&(x^{2^{k+1}}-a^{2^{k+1}})\\ =&(x^{2^k}-a^{2^k})(x^{2^k}+a^{2^k}) \\ =&(x^{2^k}-a^{2^k})(x^{2^k}-(\zeta^{n/2^{k+1}}a)^{2^k})\end{align}$$と因数分解できます。\(f \bmod (x^{2^{k+1}}-a^{2^{k+1}})\) から \(f \bmod (x^{2^k}-a^{2^{k}})\), \(f \bmod (x^{2^k}-(\zeta^{n/2^{k+1}}a)^{2^k})\) を求めるにはそれぞれの mod を取ればよいです。通常、多項式の mod は掛け算以上に計算量が掛かりますが、FFTでは単純な形をしているので簡単です。\(f \bmod (x^{2^k}-a^{2^k})\) は \(^{2^k}x=a^{2^k}\) を、\(f \bmod (x^{2^k}-(\zeta^{n/2^{k+1}}a)^{2^k})\) は \(x^{2^k}=(\zeta^{n/2^{k+1}}a)^{2^k}\) をただ代入すればよいです。以上を再帰的に繰り返すと、計算量は \(O(\sum_{i} 2^i (n/2^i)) = O(n\log(n))\) となります。

ところで、ヴァンデルモンド行列 \((\zeta^{ij})\) の逆行列は先の \(O(n\log(n))\) のアルゴリズムを一つずつ逆行することでも得られます。\(A=f \bmod (x^{2^k}-a^{2^{k}})\) と \(B=f \bmod (x^{2^k}-(\zeta^{n/2^{k+1}}a)^{2^k})\) から \(C=f \bmod (x^{2^{k+1}}-a^{2^{k+1}})\) を復元すればよいです。ぐっとにらむと $$C=\frac{1}{2}\{A+B+(a^{-1}x)^{2^k}(A-B)\}$$ です。逆行列は \((\frac{1}{n} \zeta^{-ij})\) の形でしたから、$$C=A+B+(ax)^{2^k}(A-B)$$ とすると、\((\zeta^{ij})\) を掛ける別のアルゴリズムが得られます。この二つのアルゴリズムの手順は異なります。先のアルゴリズムが周波数間引き、こちらのアルゴリズムが時間間引きと呼ばれています。

参考文献

  1. Karatsuba, Anatolii Alexeevich. “The complexity of computations.” Proceedings of the Steklov Institute of Mathematics-Interperiodica Translation 211 (1995): 169-183.
  2. Fiduccia, Charles M. “Polynomial evaluation via the division algorithm the fast Fourier transform revisited.” Proceedings of the fourth annual ACM symposium on Theory of computing. 1972.

Published in データ構造

Comments

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA