OCaml で競技プログラミングをやっている時の知見

repo https://github.com/lmdexpr/atcoder
タイトル通りのことをメモっていく
なお、現在、OCaml が使えるコンテストはそもそも atcoder しかないと思われる

なんか知らんけど input_line_exn が死ぬというやつ
競プロやるなら scanf 使っておくのが丸い
また、二個目からの入力に対して " %d" などとして、頭にスペースを入れるのも有名(?)なハック
これで改行も含めた空白区切りを読み飛ばしてくれる
今思うと、input_line_exn も入力に対して改行文字が無いとかそういう感じだろうか
あんまり検証する気もない

Scanf との組み合わせが良くないらしい
もしかして Scanf って内側でなんかバッファしてたりするのか
今度読んでみよう
context : https://atcoder.jp/contests/arc006/submissions/38150561

https://atcoder.jp/contests/abc277/submissions/36456530 は TLE で、
https://atcoder.jp/contests/abc277/submissions/36457621 なら AC になる
つまり、 ref を使って mutable として使うと遅くて、再帰にして immutable で使うと圧倒的に速い
おそらく末尾再帰最適化によるものだと思う
ref によるオブジェクトのコピーもあるのかもだけど、多分コンパイラの最適化が ref を使うと効かないんじゃないかなあ(自信はない)

改めて、そんな訳ないだろうと思ったので検証してみたが普通に通った(なんなら ref 版の方が早い)
という訳で、TLE したのは実装が悪い

典型90を解いているログ

Atcoder では iter package ( https://ocaml.org/p/iter/1.2.1/doc/Iter/index.html ) が使用可能で、まあ、便利というか結構ないと困るレベルなので多用する。
ところで、 https://atcoder.jp/contests/typical90/tasks/typical90_bd の回答中に謎挙動を見た。
items (* Iter.t *)
|> Iter.flat_map (fun j -> ...)
|> Iter.filter (fun j -> ...)
|> Iter.map (fun j -> printf "debug print %d" j; j)
と、すると、出力には debug print の文字がなかった。
これを、
let items =
items (* Iter.t *)
|> Iter.flat_map (fun j -> ...)
|> Iter.filter (fun j -> ...)
in
items |> Iter.iter (fun j -> printf "debug print %d" j);
items
と書き直すと普通に動いた。
副作用が消えているように見えて不思議。

今思うと、Iter は継続になってるだけなので前者は実際に処理が走ってないだけだな、と思った

https://atcoder.jp/contests/typical90/submissions/37700054 TLE
https://atcoder.jp/contests/typical90/submissions/37700149 AC
TLE したコードの方が計算量としては低いのでは?と思っていたので悲しかった
未だに(何故 TLE なのか)ちゃんと分かってないけど、Iter に謎挙動が多いのでちょっと困り始めている

Iter の実装は継続なので doubleton を flat_map する辺りとか遅そうか
遅延評価みたいな挙動を期待していたが filter が全部の要素に走るのでうまくいかなさそう
例えば、take_while とかなら?
→ ダメだった。doubleton も悪さしそうだし、今となっては普通に for の方が読みやすい。

modulo 計算に便利系
let modulo = 1000000007
let (+%) a b = (a + b) % modulo
and (-%) a b = (a - b + modulo) % modulo
and( *%) a b = a * b % modulo

functor にした版
ちょっと遅くなる
inverse/power を追加
(-) は unused で怒られることが多いので一旦削除
module Modulo (M : sig include Int_intf.S val modulo : t end) = struct
open M
let rec inverse ?(b = modulo) ?(u = one) ?(v = zero) a =
if b = zero then (u % modulo + modulo) % modulo
else
let t = a / b in
let a, b = b, a - t * b in
let u, v = v, u - t * v in
inverse ~b ~u ~v a
let ( / ) a b = a * inverse b
let ( + ) a b = (a % modulo + b % modulo) % modulo
let ( * ) a b = (a % modulo * b % modulo) % modulo
let power a b =
Array.init num_bits ~f:Fn.id
|> Array.fold ~init:(one, a) ~f:(fun (p, q) i ->
if b land (one lsl i) <> zero then p * q, q * q
else
p, q * q
)
|> Tuple2.get1
let ( ** ) a b = power (a % modulo) (b % modulo)
end
使用例
module Modulo998244353 = Modulo (struct include Int let modulo = 998244353 end)

let ( + ) a b = (a % modulo + b % modulo) % modulo
let ( * ) a b = (a % modulo * b % modulo) % modulo
let comb n r =
let rec loop z i =
if r < i then z
else loop ((n - r + i) / i * z) (i + one)
in
loop one one
combination 追加

Iter 使うのに便利系
let (let+) x k = Iter.flat_map k x
and (let*) x k = Iter.map k x
and (let^) x k = Iter.filter_map k x
最後はfilter
にすることもある

Graph 用
module Graph = struct
include Hashtbl
let push v = function
| None -> Iter.singleton v
| Some acc -> Iter.cons v acc
let push g v u = update g v ~f:(push u)
let connect g v u = push g v u; push g u v
let around g v = find g v |> Option.value ~default:Iter.empty
end
let g = Graph.create ~size:n (module Int)
let () =
for _ = 1 to m do
scanf " %d %d" @@ Graph.connect g
done
Iter じゃなくて普通に List でも良いが、結構 Iter で使うことの方が多かった。
connect
は無向グラフ用。

演算子系。
適当書きで使ったことないので動くか知らないやつ。
let (.!()) = Graph.around
let (.!()<-) = Graph.push
let (.%()<-) = Graph.connect

SCC
module DirectedGraph = struct
type t = {
size : int;
normal : (int, int Iter.t) Hashtbl.t;
reverse : (int, int Iter.t) Hashtbl.t;
}
let create ~size = {
size;
normal = Hashtbl.create ~size (module Int);
reverse = Hashtbl.create ~size (module Int);
}
let push v = function
| None -> Iter.singleton v
| Some acc -> Iter.snoc acc v
let push g v u = Hashtbl.update g v ~f:(push u)
let push g v u = push g.normal v u; push g.reverse u v
let next g v = Hashtbl.find g.normal v |> Option.value ~default:Iter.empty
let pred g v = Hashtbl.find g.reverse v |> Option.value ~default:Iter.empty
let strongly_connected_components g =
let iterate visit f =
Iter.fold
(fun acc v -> if visit.(v) then acc else Iter.cons (f v) acc)
Iter.empty
in
let step1 =
let visit = Array.init (g.size + 1) ~f:(const false) in
let rec dfs acc v =
visit.(v) <- true;
next g v
|> Iter.filter (fun u -> not visit.(u))
|> Iter.fold dfs acc
|> Iter.cons v
in
iterate visit @@ dfs Iter.empty
in
let step2 =
let visit = Array.init (g.size + 1) ~f:(const false) in
let rec dfs acc v =
visit.(v) <- true;
pred g v
|> Iter.filter (fun u -> not visit.(u))
|> Iter.fold dfs (Iter.cons v acc)
in
iterate visit @@ dfs Iter.empty
in
Iter.(1 -- g.size) |> step1 |> Iter.flatten |> step2
end
使い方
let n = scanf "%d" Fn.id
let a = Array.init n ~f:(fun _ -> scanf " %d" Fn.id)
let g = DirectedGraph.create ~size:n
let () =
Iter.(1 -- n) |> Iter.iter (fun i -> DirectedGraph.push g i a.(i - 1))
let self_cycle =
Array.foldi a ~init:0 ~f:(fun i acc a -> acc + Bool.to_int (i + 1 = a))
let ans =
DirectedGraph.strongly_connected_components g
|> Iter.map Iter.length
|> Iter.filter (fun len -> len > 1)
|> Iter.sum
let ans = self_cycle + ans
let () = printf "%d\n" ans

めぐる式二分探索((l, r]、(r, l] でもつというやつ)
let rec binsearch ~ok left right =
if abs (right - left) <= 1L then right
else
let mid = (right + left) / 2L in
let left, right = if ok mid then left, mid else mid, right in
binsearch ~ok left right
なんか微妙に使いこなせてなくて結構な確率でバグらせる。
でも、Core の binary_search で足りない時がごく稀にあり、使わざるを得ない。

Core の binary_search
val binary_search :
?pos:int ->
?len:int ->
'a t ->
compare:('a -> 'key -> int) ->
[ `Last_strictly_less_than
| `Last_less_than_or_equal_to
| `Last_equal_to
| `First_equal_to
| `First_greater_than_or_equal_to
| `First_strictly_greater_than ] ->
'key ->
int option

let lower_bound, upper_bound =
let arr_binsearch meth x =
Array.binary_search a ~compare meth x |> Option.value ~default:n
in
arr_binsearch `First_greater_than_or_equal_to,
arr_binsearch `First_strictly_greater_than

segment tree
module SegmentTree = struct
type m = int
let idm : m = 0
let mul : m -> m -> m = max
type t = { tree: m array; size: int }
let create ~len : t = { tree = Array.create ~len:(2 * len) idm; size = len }
let product { tree; size } l r =
let rec product lp rp l r =
if r <= l
then mul lp rp
else
product
(if l mod 2 = 0 then lp else mul lp tree.(l))
(if r mod 2 = 0 then rp else mul tree.(r - 1) rp)
((l + 1) / 2) (r / 2)
in
product idm idm (l + size) (r + size)
let update { tree; size } i x =
tree.(i + size) <- x;
let left i = 2 * i and right i = 2 * i + 1 in
let rec propagate i =
if 0 < i then begin
tree.(i) <- mul tree.(left i) tree.(right i);
propagate (i / 2)
end
in
propagate ((i + size) / 2)
end

C++ などで std::set が二分探索の代わりに使えることがある。(検索の実装が実質的に二分探索になっているため)
OCaml にも当然 Set があり、使えるのだが atcoder の環境というか Core を open した環境ではちょっとした罠がある。
以下、問題のネタバレなので一応隠す
Core を open した提出(TLE) https://atcoder.jp/contests/abc265/submissions/38911563
標準ライブラリの Set を使った提出(AC) https://atcoder.jp/contests/abc265/submissions/38911641
使用例だけ抜き出すとこんな感じ。
(* 標準ライブラリ環境 *)
module SI = Set.Make(Int)
let targets : SI.t = (* ... *)
let find x = SI.find_opt x targets |> Option.is_some
(* Core 環境 *)
open Core
let targets : Int.Set.t = (* ... *)
let find x = Int.Set.find targets ~f:((=) x) |> Option.is_some
これだけ見ても分かるし、実装を見ると特に分かりやすいが、Core では便利のために ~f を受け取ることが出来、この型は'a -> bool
となっている。
つまり、二分探索になっていないのである。
よって前者は O(logN) だが、後者は O(N) の実装になっており、致命傷になる。
また、余談だが、どちらの環境でもSet
にはmem
関数が存在しており、これはどちらとも高速に動作する(O(logN))のため、今回のケースならSet.mem
を使いさえすれば良い。
同様に問題のネタバレなので一応隠す
Int.Set.mem
を使って、更にExtended indexing operators も使ってみた例

ちなみに普通に Set にも binary_search があり、使えたので、Core 的には明示的に使えというメッセージかもしれない

let remove_range set l r =
let compare = Int64.compare in
let rec go acc set l =
match Set.binary_search set ~compare `First_greater_than_or_equal_to l with
| Some x when Int64.(x <= r) ->
go Set.(add acc x) (Set.remove set x) x
| _ -> acc, set
in
go Int64.Set.empty set l
こういうことがしたい日もある(あった)

module Iter = struct
include Iter
let zip x y = flat_map (fun x -> map (fun y -> x, y) y) x
let( * ) = zip
end
module Bit_all = struct
open Iter
let on x i = x land (1 lsl (i - 1)) <> 0
let bits len = 0 -- (1 lsl len - 1)
let by_bits x = map (fun bits -> (1 -- x) |> filter (on bits) |> map (fun x -> x - 1) |> to_array)
let start x = by_bits x (bits x)
end
Bit 全探索
Iter の方は Bit 全探索じゃないときも欲しいけど、別になくても困らない
使い方は
Iter.(
Bit_all.start a * Bit_all.start b
)
|> Iter.filter (fun (i, j) -> ...)
|> Iter.iter (fun (i, j) -> ...)
みたいな感じ

module までするとだるい時はままあって、下みたいなコードをコピペするでも良い
let x = [| (* ... *) |]
let n = Array.length x
let ans =
Iter.(0 -- (1 lsl n - 1))
|> Iter.map (fun bits ->
Iter.(0 -- (n - 1))
|> Iter.filter (fun i -> bits land (1 lsl i) <> 0)
|> Iter.map (Array.get x)
|> Iter.to_array
)
|> Iter.filter_map (function
| [| (* ... *) |] -> Some ( (* ... *) )
| _ -> None
)

module Iter = struct
include Iter
let zip x y = flat_map (fun x -> map (fun y -> x, y) y) x
let( * ) = zip
end
なんとこの部分は以下の関数が既に実装されていた。

module PI = struct
type t = int * int
let compare = Tuple2.compare ~cmp1:Int.compare ~cmp2:Int.compare
let sexp_of_t = Tuple2.sexp_of_t sexp_of_int sexp_of_int
let t_of_sexp = Tuple2.t_of_sexp int_of_sexp int_of_sexp
end
module SP = Set.Make(PI)
意外と使うけどちょっと面倒な pair の set

module type M = sig
type t
val compare : t -> t -> int
val sexp_of_t : t -> Sexp.t
val t_of_sexp : Sexp.t -> t
end
module Tuple2 = struct
include Tuple2
module Make (M1: M) (M2: M) = struct
type t = M1.t * M2.t
let compare = compare ~cmp1:M1.compare ~cmp2:M2.compare
let sexp_of_t = sexp_of_t M1.sexp_of_t M2.sexp_of_t
let t_of_sexp = t_of_sexp M1.t_of_sexp M2.t_of_sexp
end
end
module S = Set.Make (Tuple2.Make (Int) (Int64))
汎用版

module Memo = struct
include Memo
let recursive m f =
let h = Hashtbl.create m in
let rec g x = Hashtbl.update_and_return h x ~f:(function
| Some v -> v
| None -> f g x
)
in g
end
メモ化再帰(Core 環境)

使い方
let fib = Memo.recursive (module Int) @@ fun self -> function
| n when n < 2 -> 1
| n -> self (n - 1) + self (n - 2)

let eratosthenes n =
let sieve = Array.init n ~f:(const 1) in
let rec eratosthenes ?(acc=Iter.empty) = function
| [] -> acc
| x :: xs ->
if sieve.(x) = 0 then eratosthenes ~acc xs
else begin
List.range ~start:`inclusive ~stop:`inclusive ~stride:x (x*x) (n - 1)
|> List.iter ~f:(fun x -> sieve.(x) <- 0);
eratosthenes ~acc:(Iter.snoc acc @@ Int64.of_int x) xs
end
in
let primes = eratosthenes List.(range 2 n) |> Iter.to_array in
for i = 1 to Array.length sieve - 1 do
sieve.(i) <- sieve.(i) + sieve.(i - 1);
done;
primes, sieve
let primes, prime_count = eratosthenes 300_005
エラトステネスの篩

let prime_count = Array.init 300_005 ~f:(const 0)
let rec sieve ?(acc=Iter.empty) = function
| [] -> acc
| x :: xs ->
prime_count.(x) <- 1;
List.filter ~f:(fun y -> y % x <> 0) xs
|> sieve ~acc:(Iter.snoc acc @@ Int64.of_int x)
let primes = sieve (List.range 2 300_005) |> Iter.to_array
let () =
for i = 1 to Array.length prime_count - 1 do
prime_count.(i) <- prime_count.(i) + prime_count.(i - 1);
done
なぜか TLE したバージョン
詳しいことは正直分かってないが、List.filter
による List の再生成が遅いとか……?

推測でしかないが、tailrec になってないとかあるのかなと思った
match とかするとダメな時がある……?

let (.!()<-) a i v = a.(i) <- max a.(i) v
こういうのがあると助かる命がたまにある

let chmin i v = dp.(i) <- min dp.(i) v
let chmax i j v = dp.(i).(j) <- max dp.(i).(j) v
DP の遷移ならこういうのでも
一般化するならとりあえずこう?
let ch f i v = dp.(i) <- f dp.(i) v
let ch f i j v = dp.(i).(j) <- f dp.(i).(j) v

こういうのも良い
module Array = struct
include Array
let ch f a i = a.(i) <- f a.(i)
let incr = ch succ
end

流石にちょっときもい
let (.++()) a i = a.(i) <- a.(i) + 1
let (.--()) a i = a.(i) <- a.(i) - 1

module Heap = struct
include Batteries.Heap
let singleton v = add v empty
let pop_min heap =
if size heap = 0 then None
else
Some (find_min heap, del_min heap)
end
Heap (priority queue)
最近の Core にはなく、core_kernel の方で実装されていたりする
使えない認識なので Batteries から持ってくる

この Heap は実装が binomial heap になっている

module Heap = struct
module X = struct
type t = int * int * int
let compare (a, _, _) (b, _, _) = Int.compare a b
end
type t = Leaf | Node of t * X.t * t * int
let empty = Leaf
let singleton k = Node (Leaf, k, Leaf, 1)
let rank = function Leaf -> 0 | Node (_,_,_,r) -> r
let rec merge t1 t2 =
match t1, t2 with
| Leaf, t | t, Leaf -> t
| Node (_, k1, _, _), Node (_, k2, _, _)
when 0 < X.compare k1 k2 -> merge t2 t1
| Node (l, k, r, _), _ ->
let r = merge r t2 in
let rank_left = rank l and rank_right = rank r in
let l, r, rank =
if rank_left >= rank_right
then l, r, rank_right
else r, l, rank_left
in
Node (l, k, r, rank + 1)
let insert t x = merge (singleton x) t
let find_min = function
| Leaf -> None
| Node (_, k, _, _) -> Some k
let del_min = function
| Leaf -> empty
| Node (l, _, r, _) -> merge l r
end
実装してみたやつ
特に計算量的に有利になったりはあまりしない認識

module Array = struct
include Array
let rec reverse a ~start ~stop =
if start < stop then begin
Array.swap a start stop;
reverse a ~start:(start + 1) ~stop:(stop - 1)
end
end
module Permutation (M: sig type t val compare: t -> t -> int end) = struct
let next a ~l ~r =
let downto_loop ~start ~stop ~p ~proc =
Iter.(start --^ stop) |> Fn.flip Iter.fold_while false
@@ fun _ i -> if p i then (proc i; true, `Stop) else false, `Continue
in
let change_to_next_permutation a ~l ~r =
downto_loop ~start:(r - 1) ~stop:l
~p:(fun i -> M.compare a.(l) a.(i) < 0)
~proc:(fun i ->
Array.swap a l i;
Array.reverse a ~start:(l + 1) ~stop:(r - 1)
)
in
downto_loop ~start:(r - 2) ~stop:l
~p:(fun i ->
M.compare a.(i) a.(i + 1) < 0 &&
change_to_next_permutation a ~l:i ~r
)
~proc:ignore
let fold arr n ~f ~acc =
let arr = Array.copy arr in
Array.sort arr ~compare:M.compare;
let rec permutations acc =
let acc = f acc arr in
let found_next = next arr ~l:0 ~r:n in
if found_next then permutations acc else acc
in
permutations acc
end
module PermutationChar = Permutation (Char)
Permutation
これがないと死ぬ(死んだ)

combination
let rec combinations ?(acc=Iter.empty) = function
| 0 -> const @@ Iter.singleton Iter.empty
| k -> generate acc k
and
generate acc len iter = match Iter.head iter, Iter.drop 1 iter with
| None, _ -> acc
| Some hd, tl ->
let acc = combinations (len - 1) tl |> Iter.fold (fun acc cs -> Iter.snoc acc (Iter.cons hd cs)) acc in
combinations ~acc len tl

iter の要素が要素になりうる長さ n のリストを作る。
意外と困る。
let rec cartesian_power iter n =
if n <= 0 then Iter.singleton []
else
Iter.flat_map
(fun rest -> iter |> Iter.map (fun d -> d :: rest))
(cartesian_power iter @@ n - 1)

let a = [| 1; 2; 3; 4; |]
let cumsum ~init ~f a =
let paired f a b = let r = f a b in r, r in
Array.folding_map a ~init ~f:(paired f)
let cumsum = cumsum ~init:0 ~f:Int.(+) a
let () =
Array.iter cumsum ~f:(printf "%d ")
(* output: 1 3 6 10 *)
累積和
初期値が含まれないことに注意

初期値を含めるときはこんな感じ
let cumsum ~init ~f a =
let paired f a b = let r = f a b in r, r in
Array.append [| init |] @@ Array.folding_map a ~init ~f:(paired f)

二次元累積和
let a = [|
[| 1; 2; 3; 4; |];
[| 5; 6; 7; 8; |];
|]
let cumsum ~init ~f a =
let paired f a b = let r = f a b in r, r in
Array.append [| init |] @@ Array.folding_map a ~init ~f:(paired f)
let cumsum2d ~init ~f a =
let n = Array.length a and m = Array.length a.(0) in
let a = Array.map a ~f:(cumsum ~init ~f) in
for i = 1 to n - 1 do
for j = 0 to m do
a.(i).(j) <- f a.(i).(j) a.(i-1).(j)
done
done;
Array.append [| Array.create ~len:(m+1) init |] a
let cumsum = cumsum2d ~init:0 ~f:Int.(+) a
let () =
Array.iter cumsum ~f:(fun cumsum ->
Array.iter cumsum ~f:(printf "%d ");
printf "\n";
)
(* output:
0 0 0 0 0
0 1 3 6 10
0 6 14 24 36
*)
こういう計算になる (1-index)
let solve (a, b) (c, d) =
let a, b = a - 1, b - 1 in
cumsum.(c).(d) - (cumsum.(a).(d) + cumsum.(c).(b)) + cumsum.(a).(b)

imos
module ImosMake (M : sig include Int_intf.S end) = struct
type t = M.t array
let init n : t = Array.create ~len:(n + 2) M.zero
let update t (x, y) =
let l, r = min x y, max x y in
t.(l) <- M.succ t.(l);
t.(r) <- M.pred t.(r)
let finish imos : t =
let cumsum ~init ~f a =
let paired f a b = let r = f a b in r, r in
Array.folding_map a ~init ~f:(paired f)
in
cumsum ~init:M.zero ~f:M.(+) imos
let create n xs : t =
let t = init n in
List.iter xs ~f:(update t);
finish t
end
module Imos = ImosMake(Int)
let imos =
Imos.create len @@
List.init n ~f:(fun _ ->
scanf " %d %d" @@ fun a b -> a, b + 1
)

2d imos (not tested)
module Imos2dMake (M : sig include Int_intf.S end) = struct
type t = M.t array array
let init n : t = Array.make_matrix ~dimx:(n+2) ~dimy:(n+2) M.zero
let update (t:t) ((a, b), (c, d)) =
let c, d = c + 1, d + 1 in
t.(a).(b) <- M.succ t.(a).(b);
t.(a).(d) <- M.pred t.(a).(d);
t.(c).(b) <- M.pred t.(c).(b);
t.(c).(d) <- M.succ t.(c).(d)
let finish imos : t =
let cumsum ~init ~f a =
let paired f a b = let r = f a b in r, r in
Array.folding_map a ~init ~f:(paired f)
in
let cumsum2d ~init ~f a =
let n = Array.length a and m = Array.length a.(0) in
let a = Array.map a ~f:(cumsum ~init ~f) in
for i = 1 to n - 1 do
for j = 0 to m do
a.(i).(j) <- f a.(i).(j) a.(i-1).(j)
done
done;
Array.append [| Array.create ~len:(m+1) init |] a
in
cumsum2d ~init:M.zero ~f:M.(+) imos
let create n xs : t =
let t = init n in
List.iter xs ~f:(update t);
finish t
let area t a b c d =
let open M in
t.(c).(d) - (t.(a).(d) + t.(c).(b)) + t.(a).(b)
end
module Imos2d = Imos2dMake(Int)

正三角形の場合
module ImosTriangleMake (M : sig include Int_intf.S end) = struct
type t = M.t array array
let init n : t = Array.make_matrix ~dimx:(n+5) ~dimy:(n+5) M.zero
(* (a, b), (a + x, b), (a + x, b + x) を頂点とする正三角形 *)
let update (t:t) (a, b, x) =
t.(a).(b) <- M.succ t.(a).(b);
t.(a).(b+1) <- M.pred t.(a).(b+1);
t.(a+x+1).(b) <- M.pred t.(a+x+1).(b);
t.(a+x+1).(b+x+2) <- M.succ t.(a+x+1).(b+x+2);
t.(a+x+2).(b+1) <- M.succ t.(a+x+2).(b+1);
t.(a+x+2).(b+x+2) <- M.pred t.(a+x+2).(b+x+2)
let finish imos : t =
let cumsum ~init ~f a =
let paired f a b = let r = f a b in r, r in
Array.append [| init |] @@ Array.folding_map a ~init ~f:(paired f)
in
let cumsum2d ~init ~f a =
let n = Array.length a and m = Array.length a.(0) in
let a = Array.map a ~f:(cumsum ~init ~f) in
for i = 1 to n - 1 do
for j = 0 to m do
a.(i).(j) <- f a.(i).(j) a.(i-1).(j)
done
done;
for i = 1 to n - 1 do
for j = 1 to m - 1 do
a.(j).(i) <- f a.(j).(i) a.(j-1).(i-1)
done
done;
Array.append [| Array.create ~len:(m+1) init |] a
in
cumsum2d ~init:M.zero ~f:M.(+) imos
let create n xs : t =
let t = init n in
List.iter xs ~f:(update t);
finish t
let count_area =
Array.fold ~init:0 ~f:(fun acc row ->
acc + Array.count row ~f:M.(fun x -> zero < x)
)
end
module ImosTriangle = ImosTriangleMake (Int)

module Fenwick = struct
module M = Int
type t = { n : int; a : M.t array }
let create n = { n; a = Array.create ~len:n M.zero }
let add t i x =
let rec go i = if i < t.n then (
t.a.(i) <- M.(t.a.(i) + x);
go (i lor (i + 1))
) in
go i
let sum t l r =
let rec go i acc =
if i < 0 then acc
else
go (i land (i + 1) - 1) M.(acc + t.a.(i))
in
M.(go Int.(pred r) zero - go Int.(pred l) zero)
end

TLE するので何か壊れていそう
fixed

Ratio type
let rec gcd =function
| 0, b -> b
| a, 0 -> a
| a, b -> gcd (if a < b then a, (b % a) else (a % b), b)
let gcd = Tuple2.curry gcd
module Ratio = struct
type t = { num : int; den : int }
let reduce { num; den } =
let g = gcd num den in
{ num = num / g; den = den / g }
let (/) num den = reduce { num; den }
let (=) { num = num1; den = den1 } { num = num2; den = den2 } =
num1 = num2 && den1 = den2
end

https://github.com/ocaml/num でもいいが、使い慣れていないので自作の方が安心できる
(ちょっとレガシーだし)

let isqrt n =
let open Big_int in
big_int_of_int64 n
|> sqrt_big_int
|> int64_of_big_int
let div n d =
let open Int64 in
if (n >= zero && d > zero) || (n <= zero && d < zero) || n % d = zero then n / d
else
n / d - one
通常、float に変換してFloat.sqrt
を利用していたが誤差で死んだ。
big_int に実装されているものは他言語のものに近いようなのでそれを使う。
また、OCaml の (/) は rem
python の // のようなものが欲しいなら実装するしかないのでした。
FYI https://zenn.dev/mod_poppo/articles/integer-division#quot-%2F-remでdiv-%2F-modを実装する
Int64.(//)
はまた異なる実装 (float が返却される) なことにも注意

RLC
let run_length_compress a =
let n = Array.length a in
let rec loop i now (len, acc) =
if n <= i then Iter.snoc acc (now, len)
else
loop (i + 1) a.(i) @@
if now = a.(i) then len + 1, acc
else
1, Iter.snoc acc (now, len)
in
if n = 0 then Iter.empty
else
loop 1 a.(0) (1, Iter.empty)

AOJ の OCaml バージョンが低い & ライブラリがないので代替を自分で書く必要がある。
例えば、Iter の代わりに Seq を使うにしても以下の通り。
(find
, forever
, take_while
などは 4.14 からなのでバージョンが足りない…range
は今でもないが、本来ints
, take_while
で実装できる)
module Seq = struct
include Seq
let rec range start stop () =
if start >= stop then Seq.Nil
else
Seq.Cons (start, range (start + 1) stop)
let rec find f seq = match seq () with
| Seq.Nil -> None
| Seq.Cons (x, _) when f x -> Some x
| Seq.Cons (_, xs) -> find f xs
let forever f =
let rec loop () = Seq.Cons (f (), loop) in
loop
let rec take_while f seq () = match seq () with
| Seq.Nil -> Seq.Nil
| Seq.Cons (x, xs) when f x -> Seq.Cons (x, take_while f xs)
| Seq.Cons _ -> Seq.Nil
end

余談だが seq の lazy な仕組みはちょっと面白い。
この辺りで面白い議論: https://discuss.ocaml.org/t/io-monad-for-ocaml/4618/11


まずは bound が羨ましかったので実装してみる。(結局使わないので折りたたみ)
これで 0-indexed, 1-indexed の両方に対応できる。
でも、ちょっとゴツいので使わなさそー。
module BArray = struct
include Array
type 'a t = {
array : 'a array;
b0 : int; bn : int;
default : 'a;
}
let create ~default ~bound =
let b0, bn = bound in
{
b0; bn; default;
array = create ~len:(bn - b0 + 1) default;
}
let (.!()) { array; b0; bn; default; } k =
if k < b0 || bn < k then default
else
array.(k-b0)
let (.!()<-) { array; b0; _; } k v = array.(k - b0) <- v
end

これも使わないバージョンだが記録のために残す
結局 bounded は使わずで実装。
次元のパラメトライズが出来てない。
let bfs ~around ~f ~default (init_k, init_v) (b0, bn) =
let ba = Array.create ~len:(bn - b0 + 1) default
in
ba.(init_k-b0) <- init_v;
let rec bfs q =
Fqueue.dequeue q |> Option.iter ~f:(fun (v, q) ->
around v
|> Iter.filter (fun u -> ba.(u-b0) < init_v)
|> Iter.fold (fun q u -> ba.(u-b0) <- f ba.(v-b0); Fqueue.enqueue q u) q
|> bfs
)
in
bfs @@ Fqueue.singleton init_k;
fun v ->
if v < b0 || bn < v then default
else
ba.(v-b0)

Hashtbl で良くね?と思ったバージョン。
これなら次元についても問題なさそう。
速度も特に問題ない。
let bfs m ~around ~f (init_k, init_v) =
let tbl = Hashtbl.create m in
Hashtbl.set tbl ~key:init_k ~data:init_v;
let rec bfs q =
match Fqueue.dequeue q with
| None -> tbl
| Some (v, q) ->
let data = f @@ Hashtbl.find_exn tbl v in
around v
|> Iter.filter (fun u -> Hashtbl.find tbl u |> Option.is_none)
|> Iter.fold (fun q u ->
Hashtbl.set tbl ~key:u ~data;
Fqueue.enqueue q u
) q
|> bfs
in
bfs @@ Fqueue.singleton init_k

解答