競技プログラミングのためのF#入門
公式解説通りに実装します. 今回のポイントは破壊的・非関数型的な実装です. F#には独自のキュー(関数型のキュー)がなく, .NETの破壊的なキューしかありません. 関数型のキューを自前実装するのも面倒です. 余程の強烈なこだわりでもない限り素直に.NET実装を使えばいいでしょう.
念のため書いておくと, いわゆる関数型言語でも効率が必要な場合は破壊的な実装を使います. 関数の内部など影響範囲が確実に限定されていれば問題はなく, それを突き詰めて関数とアクションの分離にまで持ち上げたのがHaskellです. AtCoderでHaskellの実装を見るとシンプルな実装はしづらいようです.
さらについでに書いておくと, 少なくともAtCoder上のOCaml勢は破壊的な実装を厭いません. 困ったらOCaml勢の実装も参考にしましょう. 今回も解説用のコードはOCamlを参考にしました.
入出力とその変数は次のようにします.
1 2 3 4 5 |
|
まずはキューを初期化しつつ, フラグ管理・操作回数管理用の配列を作ります. F#では配列の配列
ではなく二次元の配列
があり, 今回は二次元の配列が便利そうなのでこちらを使います.
1 2 3 |
|
Da
が本体のループでゴリゴリ書き換える変数です. そもそもとしてF#の配列は破壊的なデータ型で, mutable
をつけなくてもDa.[j,i] <- 0
で破壊的に変更できます. Da
は.
を-1
, #
を0
で初期化しています. 最終的に何回目の処置で黒に変わったかを表す二次元配列で, -1
はまだ書き換えできていない状態を表します.
さらに#
の場所もキューに積みます. ここでAa
のi,j
はそれぞれy
座標とx
座標で混乱するため, 上記のDa
とq
は添字を入れ変えてx,y
と書けるようにしています.
let mutable ans = 0
で最終的に返す変数を宣言します. あとは適宜q
に積みつつq
が尽きるまでループします. q
に値があるかどうかはプロパティq.Count
で判定できます.
1 2 3 4 |
|
いろいろな処理の部分を考えます. 何はともあれq
から値をポップします.
1 |
|
.NETではq.Dequeue()
です. ポップした値を中心にして周囲4マスの値を確認します. 周囲4マス分の移動を表す配列Ma
を用意します.
1 |
|
さらに配列の範囲外参照と訪問状態をチェックする関数を準備します.
1 |
|
ここで白(.
)かどうかの判定は初期値ではなく書き換えたかどうかを判定したいため, 入力のAa
ではなくDa
の値で判定します. 一緒に配列の範囲内か確認するべく値Da.[x,y]
ではなく配列自体を渡しています.
この二つを使って次のように周辺4マスの状態を確認しつつ値を更新します.
1 2 3 4 |
|
Ma
の各値をArray.iter
で参照します. 周囲4マスのどれかを表すx,y
を準備してx,y
の値が白(.
)かどうかを判定します. 点x,y
がまだ白ならDa.[x0,y0]+1
でDa.[x,y]
を更新します. 最後に新たに黒(#
)にしたフラグ立てとしてq.Enqueue(x,y)
でキューにx,y
を積みます.
途中ans
をバリバリ書き換えているためこれで最終的な解答になるか微妙な不安がありますが, 都度キューに積んだ値を見てループしているため, これで常に最新の変更回数が取れています.