例えば
2重forループが思い浮かぶところですが、pythonのforループが激遅いというのは有名です。
(正確には「配列インデックスアクセスが遅い」「基本的な演算に毎回型チェックが入るから遅い」などの複合的な要素があるのでやや語弊がある表現ではあります。)
pythonで単純な処理をループする場合の高速化の手段のひとつに、numpyがあります。
一方で、numpyで簡潔に書ける処理はある程度限定されており、全ての計算をnumpyで書くのは難しい場合もあります。そのときに使える考え方のひとつに、forループ1重分をnumpyにするというものがあります。具体例を見ていきましょう。
二項係数の生成
値が非常に大きくなる(
二項係数の計算方法もいくつかの方法がありますが、今回は漸化式
31秒かかりました。これをnumpyで高速化します。漸化式に注目すると、
comb[1:, 1:] = comb[:-1, 1:] + comb[:-1, :-1]
という式が思い浮かびますが、これでは上手くいきませんね。comb[
そこで、それぞれの行ごとにはループ処理をしたまま、各行をnumpyで計算してあげます。
1秒で計算が終わっており、約30倍の高速化に成功しました。
前者のコードでは、「ループ」や、ループ変数による配列参照が
一方で、後者のコードでは、それらは
<まとめ>
★ 2次元ループ を 1次元ループに変更することで、ループに起因するボトルネックのほとんどを解消できる。
★ したがって大きな軸1方向についてnumpyが使えれば、巨大な2次元配列は十分高速に処理できる。
問題例
最近私がAtCoderで経験した例になります。ネタバレを含むのでご注意ください。
いずれも 2019/06/19 現在で、Python, PyPyでの正答提出者の中で最も実行時間の短いコードを実現しています。
AtCoder ABC 130-E Common Subsequence
次のような漸化式によって、dpおよびdp_cumを更新していく問題です。
実際の実装では、上述の
AtCoder ABC 018-C 菱型カウント
AtCoder AGC 033-A Darker and Darker
この2問は、ほとんど同じ問題です。各マスが、最寄りの黒マスからどれだけの距離にあるかを調べる問題です。黒マスからの距離を、
・左にしか移動できないとして考えた数値
・左・右にしか移動できないとして考えた数値
・左・右・上に…
と更新していきます。各段階での更新では、
という種類の漸化式を使うことになります。これを