はじめに
競技プログラミング(AtCoder)をしていると、水レートあたりから顔を出し始めて、青になってくるとみんな知ってる、そんなアルゴリズム、畳み込み。僕もようやくこれに手を付けて、少しばかり勉強して、自分のライブラリに入れました。
畳み込み自体は数年前から知っていたのですが、複素数やら、整数環やらと出てくるとかなり構えてしまい、また読み進めると何をやっているのかよくわからなくなって、幾度も理解を諦めていました。今回の記事では、よくある他の記事とは順序を変えて、僕が理解した順序で説明を書いてみます。複雑な数学的背景は可能な限り飛ばしますが、どうしても数式を使うことは避けられないので、その点は頑張ってください。また、具体的なコードの実装の説明も行いません。あくまでも理論的な部分を理解する土台を作るための記事です。
またtatyamさんが、前にも畳み込みに使われるアルゴリズムであるFFTの解説を書いて下さっています。この記事よりも正確になっているので、数式が全く怖くない、FFTの仕組みをしっかり理解したい、という方はこちらを見た方がいいかもしれません。

畳み込みとは
定義
原義的な畳み込み、特に離散的な方の畳み込みは、次のようにして定義されます。
言葉で言うと、二つの関数の引数の和が一定になるようにして足し合わせたものです。ここで出てきた関数 や は、この記事では数列と解釈して差し支えないでしょう。そこで、この畳み込みを二つの数列とに対して定義して、その結果をとします。すると、次のように書くことができます。
競技プログラミングで出てくる畳み込みは、この式で計算される を、定義できる全てのについて求める計算を指します。
計算量
この畳み込みを計算することを考えます。各について個の総和を計算することになるので、時間計算量はになります。これでは遅いので、もう少し早くしたいところです。線形時間とは言いませんが、程度になってくれると嬉しい。
そしてこの計算量は実現可能で、これを実現するのが 高速フーリエ変換 (FFT) と呼ばれるアルゴリズムです。ここからは、畳み込みがどう高速化されているのか、FFTは何に使われるのかをゆっくりと理解していきましょう。
数列から多項式へ
畳み込みを理解する前に、少し数列の表現方法について説明します。
係数による表現
フーリエ変換を用いた畳み込みの計算では、数列を多項式として解釈します。つまり、長さの数列があるとき、これを多項式で表される次の多項式として見ます。こうすると、畳み込みを多項式の積として解釈できます。
例として長さが3と2の数列とを用意し、それぞれに対応する多項式を、とします。この積を計算してみましょう。
各項の係数についている添え字に注目します。すると、どの項でもの添え字との添え字の和がその項のの次数に一致することがわかります(2乗の項のはと解釈する影響で消えてしまっています)。特に、の乗の項の係数をとしてとすると、はとの畳み込みになっています。
値による表現
前項では数列から多項式を作ることができることがわかりました。今度は、一つの多項式を数列で表現することを考えます。各行の係数を取ってきた数列は、元の多項式をよく表現してくれます。ところで、多項式を表現する、一つに指定する方法はそれだけでしょうか?
直線は1次の多項式を使って表すことができます。そして、その直線を特定するには、その直線が通る2点の情報があれば十分です。2次関数なら2次の多項式を用いて、通過する3点があればよく、3次関数では3次の多項式で4つの通過点があると特定できます。

一般に、次の多項式の各係数は、グラフの通過する点が個あれば求めることができます。言い換えると個(以上)の点の情報があれば、一つの多項式を表現できるということです。
尚、各多項式がどの数列によるものなのかをわかりやすくするため、数列から作られた多項式を単に、その出力値から得られる数列をとします。
(数列と混同しないよう気を付けてください)。
多項式の掛け算
さて、数列から作られる多項式の表現として
- 係数からなる数列
- 多項式の出力値からなる数列
の2種類を得ることができました。今回やりたいことである畳み込みは多項式の積と対応するので、多項式の積を取ったときにこれらの数列がどう変わるのかを調べてみます。これまで通り、数列とからなる多項式とを考えます。
まず、係数からなる数列は先述の通り二つの数列の畳み込みとなります。問題は出力値の方です。こちらは単なる値の積となってくれます。つまり、について、
です。で定義したので当然ですが、こちらはかなり簡単になりました。とさえわかればの値が即座にわかります。
問題の変換
と、積の計算が随分と楽になりました。しかし、これで終わりではありません。ここまで言及していなかった、重要な事項がいくつかあります。
まずはを実際に計算するときの計算量です。は次の多項式ですから、この値を愚直に計算するにはの時間計算量が必要になります。これを必要なだけ求めると、の計算には最終的に時間必要となり、高速化は実現されていません。
第二に、からへの変換に使う引数の決定です。どのような値を使えばよいでしょうか?
第三に、からへの変換です。ここまで、からの変換方法は説明しましたが、その逆はまだです。
次の章からは、数列の畳み込みを多項式の積と解釈したことによって出たこの課題を解決する手段を説明します。少しフライングすると、を適切に決めることで、これらの問題を一気に解決します。
高速フーリエ変換
この章では原始根だの離散フーリエ変換だのはだいたいすっ飛ばすので安心してください。
この章では原始根だの離散フーリエ変換だのはだいたいすっ飛ばすので安心してください。
入力 の決定
要素の数列からを計算することで、畳み込みの計算が1歩進むことがわかりました。ここからはその計算方法を実際に追っていきます。
まずはへの入力とするの決定です。はどんな値がいいでしょうか?まず、異なる個の点が欲しいので、も互いに異なっていてほしいですね。他にどんな条件があるといいでしょうか。離散フーリエ変換で用いられるは、追加で次の性質を持っています。
- は次の性質を満たす数の乗
- (が偶数のとき)
アルゴリズムの解説に集中したいので、この記事では具体的なの値は説明しません。ほとんどのFFTの解説記事には書いてあるので、気になる人は調べに行ってみましょう。
の計算
では、このような値を使って計算したとき、どんなことが起こるでしょうか。
ここでは、を偶数として計算をしてみます。(もしが偶数じゃないなら、数列に0の項を一つ付け足せば良いです)
まず、は ですから、
と書けます。が偶数なので、この総和についてが偶数の項と奇数の項とで分けてみます。
ここの第二項について、をくくって
となります。では同じ計算を、についても行ってみると、なんやかんやあって (をに変えるだけです)
となります。周りは、の持つ性質、、を使ってさらに整理できて
となります。
この二つを見比べてみると、なんと足し引きが違うだけでほとんど同じであることがわかります。試しに
なんて書けば、
となります。つまり、実質的に計算が必要なが半分になっています。
そして、やの式を見ると、これもまた同じ出力値の計算になっていることがわかります。
再帰計算
少し詳しく書きます。 として、を考えます。はのちょうど半分から作っているので、その要素数が個であることに注意すると、計算に使うとしてを使うことができます。実際、
となって、に対する計算で使ったと同様の性質を満たします。
では計算してみましょう。を求めます。
となって、確かにがとなっています。同様に、です。
ここまでをまとめると、(要素)を求める問題を、と (要素)を求める問題に分割することができました。もしやも偶数個からなる場合、これをさらに分割できます。もしが2冪であれば、この分割を繰り返すことができて、高速に計算できそうです。特に、数列の末尾に0をつけ足しても結果は変わらないので、はいつでも2冪にすることができます。ヤッター

こんな風に偶奇で分割して計算していくのをバタフライ演算とか呼んだりするらしいです。僕にはどこが蝶なのかわからなかった...
これによる計算量を見積もります。をとに分割して同じ問題を解いた後、はその結果とから各要素を定数時間で計算できる()ので、後半の計算はとなります。そして、数列の分割は段階まで出来るので、からの計算は全体で時間で計算できました。
尚、計算を二つに分け、分割しながら計算したこのアルゴリズムを高速フーリエ変換 (FFT) と呼びます。
逆変換
ここで目的を再確認しましょう。当初の目的は数列との畳み込みを計算することです。そのため、やで作られる多項式との積を考えました。ここで、やにいくつかの値を代入して得られる数列とを使うと、を表現しているを簡単に求めることができます。
ここまで、そのうち、もといからを高速に計算する方法を解説しました。あとはからを復元し、を得ることができれば目的達成です。
結論から書くと、この逆変換は
です。
これが正しいことを実際に確かめます。まずは左辺にの計算式を代入します。
和の順序を入れ替えて
ここで出てきたをさらに計算します。
のとき、なので、です。
なら、は初項、公比の等比数列の和になります。(はにならないという性質を持ちます。)
そのため、等比数列の和の公式から
です。以上まとめると、
はの場合だけが残って、
となって、確かに逆変換が成立していることがわかりました。
順変換との差異
との相互変換を求めることができました。
2つの式を比べてみます。
まず逆変換の方は、和の後にで割っています。もう一点、がに置き換わっていますね。
それ以外は順変換と逆変換は何も変わっていません。はい、何も変わっていません。つまり、順変換のアルゴリズムを逆変換にも使うことができ、順変換、逆変換ともに高速に計算出来るのです。
まとめ
さて、ここまでをまとめます。
二つの数列とについて、その畳み込みを求めることを考えます。
まず、整数を、以上となる2の累乗で求めて、との長さをにします。(新しい部分は0で埋める)
次にから
で得られるを高速フーリエ変換により計算し、同様にも求めます。これら二つから
でが得られます。
これに対して、
で得られるを再び高速フーリエ変換により計算することで、最初の目的を達成することができました。
以上のアルゴリズムが高速フーリエ変換により畳み込みを高速に計算する方法です。
最後に
(説明のまとめに「まとめに」を使ってしまったので「最後に」です)
お疲れさまでした!!!
🎉これであなたも畳み込み・高速フーリエ変換を理解できました!🎉
と言ってみたものの、実はまだ一つ巨大な問題が2つほど残っています。
まず一つ目は、今回の記事で出てきた謎の数の正体です。個人的に、このがFFTの理解における一番の壁だと思っています。水や青コーダーでこの類の分割統治につまずく人はほとんどいませんからね。今回、は単なる都合のいい数としてしか出ていません。しかし、その性質だけを抑えておくことで、難しい部分をスキップして説明をすることができています。
二つ目は、この畳み込み、およびFFTを処理する実装です。この記事では理論的な部分の(それも一部の)説明にとどめています。今回説明しなかったこれら二つはまた別の記事としていずれ出す、かもしれません。しかし、ここまで理解出来たら、の理解も、実装の理解も容易いはずです。
では、またの機会に。
参考

