競技プログラミングのためのF#入門
公式解説の他にもいくつか解答を見ていると, 偶奇で場合分けをしているコードもあれば, 一本で計算しきっているコードもありました. ただ私にはあまり直観的でなくすっと理解しにくかったため, ここではcojnaさんのコード例を参考にした実装を紹介します.
大きな数値から小さな数値を引いた方がよいのは明らかです. ソートして大きい数のリストhs
と小さい数のリストls
にわけ, 大きい数と小さい数を順に並べ続ければいいでしょう. 特に分けたリストを再帰的に先頭から取れば十分です.
問題は全要素数が奇数の場合の処理で, hs
とls
から一つずつ取り続けて余った項をどこに置くかが問題です.
入力例1で上の方針を追いかけます.
1 |
|
まずは「ソートして大きい数のリストhs
と小さい数のリストls
にわけ」の部分を実装します.
1 2 3 4 5 6 |
|
大きい方は大きい順に取るためList.rev
で順序を反転します.
1 2 3 4 5 |
|
あとはxs
とzs
の先頭から再帰的に処理します. 再帰関数をfrec
として次のような呼び出しを前提にしましょう.
1 2 3 4 |
|
はじめから初項を分離しなくてもいいとは思いますが, cojnaコードに合わせています. h-l
の部分が結果を積むaccumlator変数です. h
はhigh, l
はlowで値の大小の分割を表しています. 最後の(ls,hs)
はタプルにせず分けても構いません. 次の実装を前提にタプルにしています.
さて, 再帰関数の本体を考えましょう.
1 2 3 4 |
|
function
の部分は次の省略表記です.
1 2 3 4 5 |
|
これは次のように書いても構いません.
1 2 3 4 5 |
|
以下前者のコードを前提に考えます. まずパターンマッチのmatch
の第一行から考えましょう.
1 2 |
|
まさにリストを先頭と残りに分割して処理しています. 再帰呼び出しのacc + h-low + high-l
の項は, 一つ前の先頭の項に対して大きい数と小さい数の差を取るそのままの処理です. low
, high
, xs
, ys
の項も分割した項をそのまま積めば問題ありません.
残り二行を確認しましょう.
1 2 3 4 |
|
[],[h]
は項数が奇数の場合の余りの処理で, 最後の_,_
が項数が偶数の場合の処理です. 後者は積み切った値を素直に返せばよく何も考える必要はありません. したがってあとは一つ余った項の処理だけです.
結論から言えばmax (h-low) (high-h)
です. はじめにsplitAt (N/2)
でわけました. この分け方で最後の項がls
とhs
のどちらに入るか変わります. どうしても揺れが起こるためmax
でその揺れを吸収しています.
入力例1と新たに作った以下のもう一つの入力例をもとに確認しましょう.
入力例1での最後の余りの処理は次のようになります.
1 2 3 4 5 6 7 8 9 |
|
したがってこちらはhigh-h
を取るべきです. 具体的に全体としてどのような並び方を選んだのかを考えるのも大事です. 実際には次のようになっています.
h-low
: 8 1 6 2 3
high-h
: 1 8 2 6 3
つまり初項を大きい方から取るか, 小さい方から取るかが最後の取り方で決まります.
さて, 新たな入力例はlet Aa = [|1L;4L;5L|]
とします. この余りの処理は次のようになります.
1 2 3 4 5 6 7 8 9 |
|
入力例1と違ってh-low
を取るべきです. 具体的に全体としてどのような並び方を選んだかと言えば次の通りです.
h-low
: 5 1 4
high-h
: 1 5 4
もちろん他の可能性がないかも考えるべきではありますが, 前の項との差を取るアルゴリズムの組み方からしてありうるのはこの二通りしかありません. あとはこれを一般的にきちんと書き切れば適切なコードができます.
Previous: 068 D - String Equivalence Next: 070 C - Snuke Festival back to top