競技プログラミングのためのF#入門
GitHub上の対応ディレクトリ 公式ページ 要点: アルゴリズムの検討 解説1, DFS
公式解説通りにDFSで実装します.
入出力
| let N = stdin.ReadLine() |> int
let Aa = Array.init (N-1) (fun _ -> stdin.ReadLine().Split() |> fun x -> int x.[0], int x.[1], int64 x.[2])
let Q,K = stdin.ReadLine().Split() |> Array.map int |> (fun x -> x.[0],x.[1])
let Xa = Array.init Q (fun _ -> stdin.ReadLine().Split() |> Array.map int |> fun x -> x.[0],x.[1])
solve N Aa Q K Xa |> Array.iter stdout.WriteLine
|
距離c
だけint64
にしています.
隣接リストの生成
ここまでに何度か出てきたのと同じように素直に実装すれば問題ありません.
| let Ga =
(Array.init N (fun _ -> []), Aa)
||> Array.fold (fun Ga (a,b,c) -> Ga.[a-1] <- (b-1,c)::Ga.[a-1]; Ga.[b-1] <- (a-1,c)::Ga.[b-1]; Ga)
|
DFSの実装
Array.zeroCreate N |> dfs (K-1) (-1) 0L
を前提に実装すると次のようにすっきり書けます.
| let rec dfs v p d Da =
Array.set Da v d
(Da, Ga.[v]) ||> List.fold (fun Da (u,c) -> if u=p then Da else dfs u v (d+c) Da)
|
F#の配列は破壊的なデータ型で, Array.set
はunit
を返すだけで書き換えた配列を返してくれるわけではないため, Array.set Da v d
を別立てにしています. fold
の中身は隣接リスト内の値が親ノードと一致していたら積んだ値を返し, それ以外は入力のd
に対して隣接リストが持つ距離c
を素直に足して積むだけです.
二点間の距離の計算
あとはK
からの最短距離を素直に計算します.
| Xa |> Array.map (fun (x,y) -> Da.[x-1]+Da.[y-1])
|
まとめ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | let solve N (Aa:(int*int*int64)[]) Q K Xa =
let Ga =
(Array.init N (fun _ -> []), Aa)
||> Array.fold (fun Ga (a,b,c) -> Ga.[a-1] <- (b-1,c)::Ga.[a-1]; Ga.[b-1] <- (a-1,c)::Ga.[b-1]; Ga)
let rec dfs v p d Da =
Array.set Da v d
(Da, Ga.[v]) ||> List.fold (fun Da (u,c) -> if u=p then Da else dfs u v (d+c) Da)
let Da = Array.zeroCreate N |> dfs (K-1) (-1) 0L
Xa |> Array.map (fun (x,y) -> Da.[x-1]+Da.[y-1])
let N = stdin.ReadLine() |> int
let Aa = Array.init (N-1) (fun _ -> stdin.ReadLine().Split() |> fun x -> int x.[0], int x.[1], int64 x.[2])
let Q,K = stdin.ReadLine().Split() |> Array.map int |> (fun x -> x.[0],x.[1])
let Xa = Array.init Q (fun _ -> stdin.ReadLine().Split() |> Array.map int |> fun x -> x.[0],x.[1])
solve N Aa Q K Xa |> Array.iter stdout.WriteLine
|
解説2: ダイクストラ法
他のF#解答を参考にダイクストラ法での実装も紹介します. もとのコードは二つポイントがあります.
- 破壊的な実装である.
PriorityQueue
の代わりにSet
を使っている.
もとの入力をリストで処理している一方, 私は配列で実装しています. 処理系も違うため単純な速度比較はできません. どなたか入力を合わせて私のコードと速度比較してみてください. そのうち自分で実装してみようとは思っています.
入出力
| let N = stdin.ReadLine() |> int
let Aa = Array.init (N-1) (fun _ -> stdin.ReadLine().Split() |> fun x -> int x.[0], int x.[1], int64 x.[2])
let Q,K = stdin.ReadLine().Split() |> Array.map int |> (fun x -> x.[0],x.[1])
let Xa = Array.init Q (fun _ -> stdin.ReadLine().Split() |> Array.map int |> fun x -> x.[0],x.[1])
solve N Aa Q K Xa |> Array.iter stdout.WriteLine
|
大枠
DFSと同じで次のように書けます.
1
2
3
4
5
6
7
8
9
10
11
12 | let solve N (Aa:(int*int*int64)[]) Q K Xa =
let Ga =
(Array.init N (fun _ -> []), Aa)
||> Array.fold (fun Ga (a,b,c) -> Ga.[a-1] <- (b-1,c)::Ga.[a-1]; Ga.[b-1] <- (a-1,c)::Ga.[b-1]; Ga)
let Da = dijkstra N (K-1) Ga
Xa |> Array.map (fun (x,y) -> Da.[x-1]+Da.[y-1])
let N = stdin.ReadLine() |> int
let Aa = Array.init (N-1) (fun _ -> stdin.ReadLine().Split() |> fun x -> int x.[0], int x.[1], int64 x.[2])
let Q,K = stdin.ReadLine().Split() |> Array.map int |> (fun x -> x.[0],x.[1])
let Xa = Array.init Q (fun _ -> stdin.ReadLine().Split() |> Array.map int |> fun x -> x.[0],x.[1])
solve N Aa Q K Xa |> Array.iter stdout.WriteLine
|
つまりDFSがdijkstra
に変わっただけです.
ダイクストラ法
私はまだ一般的なダイクストラ法をきちんと理解できていません. 詳しくはアルゴリズムの本を参照してください.
今回の実装に関しては次の通りです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | let dijkstra N K (Ga: (int * int64) list []) =
let Da = Array.create N System.Int64.MaxValue
Da.[K] <- 0L
let rec loop (Da:int64[]) q =
if Set.isEmpty q then (Da,q)
else
let (c,v) = Set.minElement q
let q0 = Set.remove (c,v) q
if c <= Da.[v] then
((Da,q0), Ga.[v]) ||> List.fold (fun (Da,q) (bi,ci) ->
let s = Da.[v]+ci
if s < Da.[bi] then Da.[bi] <- s; (Da, Set.add (s,bi) q) else (Da,q))
else (Da,q0)
|> fun (Da,q) -> loop Da q
loop Da (Set.singleton (0L,K)) |> fst
|
もとのコードはloop
が完全に破壊的です. このコードもDa.[K] <- 0L
やloop
の中でのDa.[bi] <- s
が厳密には破壊的なコードです. わざわざ非破壊的にするほどでもないため, 大目に見て実装しています.
ポイントは優先度つきキューの代わりにSet
を使っている点です. キュー代わりのSet
に積んだ値から最小値を取り出し, キューが尽きるまでループをくり返しています. もちろんSet
では速度は出ません. また配列のArray.set
と違ってSet.add
は更新したSet
を返してくれる非破壊的な関数です.
ちなみに.NET6
で優先度つきキューが実装されたものの, AtCoder上のF#は.NET Core 3.1.201
で使えません.
TODO 確認: 解説2とオリジナルの破壊的な実装の速度比較
Previous: 092 B - Unplanned Queries Next: 094 D - Blue and Red Balls back to top