競技プログラミングのためのF#入門
1 2 3 |
|
尺取り法で対処します. ここでは尺取り法は解説しません. オンライン上の資料としては例えばここを参考にしてください.
C/C++/Rustなど速い言語, またはHaskellでは問題ないようですが, AtCoder上のF#では単純な探索でTLEしてしまったため, 尺取り法の探索部分を二分探索で書く必要がありました.
2022/12時点の私の実装力だと, 二分探索ではまり倒したために力づくの部分があり, あまり綺麗なコードになっていません. いつかもう少しすっきり書き直したいです.
今回は親切に入力の配列がソートされているため不要です. 実際には必要に応じてソートします.
配列の各添字に対して条件をみたす最長の添字を取ればよいため, 各i
ごとに最長の添字を探す関数をsearch
とすれば実装の本体は次のように書けます.
1 |
|
条件をみたす添字がない可能性があるため, search
の返り値はOption
にしています. 特に条件をみたす添字j
に対してj-i
を積み, そうでない場合は0
にして和を取れば求める組み合わせの総和が得られます. ここで一般に総和の値はint
の範囲を越えるため, j-i
にint64
をかませる必要があります. 実際これでRE
をくらって原因がわからず30分ほどはまり倒しました.
何はともあれあとはsearch
を実装すれば終わりです.
search
の実装¶二分探索に入る前にまずは条件をみたす添字があるかどうかを判定します. ここでは次のように書きます.
1 |
|
i=N-1
の場合は後続がありません. また既にソートされているため, 配列の次の添字との差が既にK
を越えていれば条件をみたす添字がありません. したがってこの二者の場合はNone
を返します. あとは探索値が必ず存在する仮定のもとで二分探索します.
まず真偽計算用の関数を用意します.
1 |
|
各i
ごとにこれを使って次のように計算します.
1 |
|
あとはp
またはp i
を使って次のように二分探索を書きます.
1 2 3 4 5 6 |
|
いつも通り終了条件はr <= l
です. 実際に実行してみるとわかるように, 上記の実装ではl
が適切な添字の値になるとは限りません. そこでpi l = p i l
を判定して条件をみたすならl
を, みたさない場合は1
を引いたl-1
にします. (TODO: ここでスカっとl
を返したい.)
次は再帰部分です. これもいつも通りまずは中点を取るべくm = (l+r)/2
を取ります. もしIa.[m]
が条件をみたすならm+1
以上r
以下から新たに添字を探します. もしIa.[m]
が条件をみたさないからl
以上m
以下から新たに添字を探します.