今回は、Convex Hull Trickを履修したので解説記事を書きます。
Convex Hull Trick とは
COLOCON -Colopl programming contest 2018- Final-C スペースエクスプローラー高橋君
を解こうとして、わからなくて、解説を見て、詰まりました。
知らないデータ構造は勉強しなきゃだめですか?だめですね
ということで、やってみたので記事にします。
Convex Hull Trickは、次のような操作ができるデータ構造(?)です。
・直線集合に直線を追加する( $ax+b$ の形で)
・ある $x$ に対し、集合の中で最小値(最大値)をとるような直線の値を求める
愚直に実装すると、追加される直線の本数を $N$ 本、最小値クエリの数を $Q$ 回として、当然 $O(NQ)$ かかるのですが、Convex Hull Trickを使うことで、追加を含めても計算量を $O((N+Q)\log^2 N)$ (特殊な場合は $O((N+Q)\log N)$ とか $O(N+Q)$ にできます。)
全体的な方針




・追加する直線の傾きが単調減少(単調増加)の場合
先頭や末尾にしか追加をしないので、std::vectorやstd::dequeで十分です、logがひとつ落ちます。
・クエリで与えられる $x$ が単調減少(単調増加)の場合
std::dequeで先頭や末尾を削除しながらどんどん進んでいくと二分探索がいらなくなるので、logがひとつ落ちます。
・上の二つを同時に満たす場合
両方実装すればいいのでlogがふたつ落ちます。
同じような感じで、追加する直線の傾きが今までのすべての直線より大きいか小さいかのどちらかの場合、とかもできますがなかなかに意味不明なクエリなので気にしなくていいでしょう。
自分はこんな感じの、クエリの種類によって計算量が異なるタイプのデータ構造はそれぞれ最適に実装したくなるんですが、「logがふたつついてもしらん!」みたいな人は、mapだけで実装してライブラリ化してもいいと思います。
実装を分ける場合は、クラスにしてコンストラクタ引数で種類を見分けるとらくです。引数を忘れて使うとき困りそうな場合は別のクラスに継承して自分で分かる名前にしておくともっとらくです。自分はセグ木とかはこうしています。
サンプルコード
現在実装が完成している、直線追加クエリの単調性にのみ対応したものを貼ります。
Convex Hull Trick サンプル
N=100000で1.5倍以上速くなったので、logを落とすのは大事そうです。
おわりに
Convex Hull TrickはAtCoderではたぶんあまり出てきません(今更)
結構高度なアルゴリズムですが、JOI本選などで出てきそうな雰囲気の問題はいくらでも思いつき、また、最近のAtCoder企業コンテスト本戦などでも高度アルゴリズムがよく出るので書けて損はないでしょう。