yukicoder No.1036 Make One With GCD 2 に追加したケースについて
追記
4/25 Sparse Tableを落とすケースが追加されてたので記述を修正
概要
yukicoder上で出題されたこの問題にHackケースを追加しました. (99_challenge01.txtというケースれふ)
いくつかある解法のうち, セグメント木上での二分探索を行う解法に対する悪意的な入力なのれふが, 何故落ちるのかが分かりづらいようなので記事化することにしました.
GCDの計算ステップ数について
の時間計算量がであることは皆さんご存知のことと思いまふ.
しかし, それ以上の理解として関数の挙動をよく眺めると, 関数が実際に行う計算ステップ数はの値を剰余算によって小さくした回数である, ということが分かりまふ.
このことを考慮すると, 次のことがいえまふ.
セグメント木にの値を取得するクエリを投げた時,
その計算量は(ではない)
区間の計算にあたり, 個程度の区間をマージすることになりまふが, この過程で発生する「の値を剰余算によって小さくする回数」は全体で回程度であって, の係数ではないためれふ.
今回の問題に対する種々の解法の計算量について
まず, セグメント木で区間を取得可能にし, に対しを二分探索する解法がありまふが, この計算量は上で説明した通りになりまふ.
この解法は基本的にTLEになるようれふ.(ケースを追加するまでもなく落ちてました)
続いて, 二分探索の代わりに尺取り法を用いる解法がありまふ. これはであり, 高速れふ.
問題なのは, 「セグ木上の二分探索」と呼ばれるテクニックによる解法や, Disjoint Sparse Tableを用いて区間を取得して二分探索する解法れふ.
これらは基本的に高速れふが, その気になればとなるようなケースを作れまふ.
そして一方, Disjoint Sparse Tableの代わりにSparse Tableを用いた場合, 落ちるケースを作るのが難しいれふ(というより, 存在するか怪しいと思ってまふ).
どのようなケースで落ちるのか
をの計算ステップ数が大きくなるようなの組としまふ. (これは, 互除法の逆操作を考えるとフィボナッチ数列の隣接2要素を選ぶと良いれふ)
結論からいうと, 最悪ケースはこのとを交互に1024個ずつ並べたケースれふ.(個数は2べきでほどほどに大きければ基本なんでも良くて, もう少し妥当なのがあるかもしれません)
「セグメント木上の二分探索」と称されるアルゴリズムでは, 現在のノードが条件を満たす場合は右や上に, 満たさない場合は下に移動しまふ. 今回の問題における条件は「が1より大きいこと」れふ.
提示した最悪ケースにおいては, 同じ要素がある程度連続しているため, それらのみを含む区間においては条件を満たすものとしてある程度上や右に移動しまふ. そして, 条件を満たさなくなってからは, が1になるようなの境界が2べきの位置に存在しているため, 下のほうにあるノードまで移動することで探索が終了することが多いれふ. ここで, 条件を満たさない場合に行われるの計算ステップ数は, ある程度の深さより下において回となりまふ. これは, マージ元の値が上述ので, マージされる値が(あるいはその逆)となるためれふ.
このことから, 結局, セグメント木上で二分探索を行った場合, に比例する回数だけの計算が行われることになり, TLEとなりまふ.
また, これは意図していなかったのれふが, Disjoint Sparse Tableも同様に落ちるようれふ.
一方, Sparse Tableを用いて二分探索をする解法はこのケースのようなアプローチで最悪ケースを作るのは難しいれふ. Sparse Tableにおける区間クエリでは, 前計算済の区間2つがマージされまふ. ここで, 左側の区間におけるを, 右側の区間におけるを, 共通する区間におけるをとすると, マージの計算ステップ数がに比例するためには, たとえばが満たされなければなりません. しかし, 二分探索によってほとんど無作為に近い形で選ばれる区間たちすべてに対してこれを満たすような数列を構築できる気がしないため, 実際には程度で動作するのでは?と思ってまふ. (証明等をしたわけではありません)
ほとんどの要素をにし, 1024個に一つ・を交互に混ぜることで条件を満たす数列が構築出来たようれふ. (そのような類のケースが追加されていました)
https://yukicoder.me/submissions/470831
このケースへの対処法
セグ木上での二分探索解法を採用しない
はい
数列をずらす
セグ木のノードが同じ要素で埋まった場合に計算量が悪化するため, 数列の先頭にダミーの要素を挿入すると取り敢えず今回追加したケースにおいてはTLEしなくなりまふ.
フルフィードバック形式のコンテストにおいてはこれをするだけでコードを見ない限り落とせなくなりまふし, Hackのあるコンテストにおいても追加されるダミーの要素をランダムに決めるとHackは困難になりまふ. (が, 運悪くシステス等で落ちる確率はそれなりにありまふ)
一応表明しておくと, この問題に関して, 要素を挿入することでTLEを回避したコードを追いかけて対応するHackケースを追加するようなことをするつもりは(私は)ありません.
余談
もしかすると, 数列をずらす程度じゃ対処できないHackケースが存在するかもしれません. (存在しないことを示してないので)