AtCoderに登録したら解くべき精選過去問10問をOCaml / Haskell / C言語で解いてみる

AtCoderの過去問を解いてみる。

一切の部品が手に入らないので, 今年はもうダメ。
たまにはこんなこともしてみる。

AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~」という記事にある「AtCoder Beginners Selection」10問をOCaml/Haskell/C言語で解いてみた。

OCamlで書いたコードをHaskellとCに移植したので, 冗長になっている所もあるけど気にしないで。

  • “.ml”はOCaml
  • “.hs”はHaskell
  • “.c”はC言語

以下のコードはGitHub上にあります。

https://github.com/ak1211/atcoder-beginners-selection

1問目 ABC 086 A - Product

入出力の練習かな。
パイプ演算子「|>」はOCamlではよく使う。

ABC086A.ml
(* ABC086A - Product *)

let solver = function
  | [ a; b ] -> if a * b mod 2 = 0 then "Even" else "Odd"
  | _ -> failwith "?"

let () =
  read_line () |> String.split_on_char ' ' |> List.map int_of_string |> solver
  |> print_endline
ABC086A.hs
{- ABC086A - Product -}

solver :: [Int] -> String
solver xs = case xs of
  [a, b]  | even (a*b) -> "Even"
          | otherwise ->  "Odd"
  _ -> "?"

main :: IO ()
main =
  putStrLn . solver =<< input
  where
    input = map read . words <$> getLine
ABC086A.c
/* ABC086A - Product */
#include <stdio.h>
#include <stdint.h>

int main()
{
    int32_t a, b;
    scanf("%d %d", &a, &b);
    (a * b % 2) ? puts("Odd") : puts("Even");
    return 0;
}

2問目 ABC081A - Placing Marbles

1の数を数える。
OCamlはforもよく使う。
Haskell? foldかな。

ABC081A.ml
(* ABC081A - Placing Marbles *)

let solver s =
  let counter = ref 0 in
  for i = 0 to String.length s - 1 do
    if s.[i] = '1' then incr counter
  done;
  !counter

let () = read_line () |> solver |> string_of_int |> print_endline
ABC081A.hs
{- ABC081A - Placing Marbles -}

solver :: String -> Int
solver =
	foldl (\a c -> if c == '1' then a+1 else a) 0

main :: IO ()
main =
  putStrLn . show . solver =<< getLine
ABC081A.c
/* ABC081A - Placing Marbles */
#include <stdio.h>
#include <string.h>

int main()
{
    char s[4];
    gets(s);
    int counter = 0;
    for (int i = 0; i < strlen(s); ++i)
    {
        if (s[i] == '1')
        {
            counter++;
        }
    }
    printf("%d\n", counter);
    return 0;
}

3問目 ABC081B - Shift only

2で割り切れる限り2で割り続ける。
ループは再帰。

ABC081B.ml
(* ABC081B - Shift only *)

let even x = x mod 2 = 0

let half x = x / 2

let rec solver = function
  | xs when List.for_all even xs -> 1 + solver (List.map half xs)
  | _ -> 0

let () =
  let _ = read_int () in
  read_line ()
  |> Str.split (Str.regexp " ")
  |> List.map int_of_string |> solver |> string_of_int |> print_endline
ABC081B.hs
{- ABC081B - Shift only -}

solver :: [Int] -> Int
solver xs =
  if all even xs then
    1 + solver (map half xs)
  else
    0
  where
    half x = x`div` 2

main :: IO ()
main = do
  n <- readLn :: IO Int
  xs <- map read . words <$> getLine
  putStrLn . show $ solver xs
ABC081B.c
/* ABC081B - Shift only */
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

bool all(int64_t n, int64_t *a)
{
    for (int64_t i = 0; i < n; ++i)
    {
        if (a[i] & 1)
        {
            return false;
        }
    }
    return true;
}

void half(int64_t n, int64_t *a)
{
    for (int64_t i = 0; i < n; ++i)
    {
        a[i] = a[i] >> 1;
    }
}

int64_t solver(int64_t n, int64_t *a)
{
    int64_t counter = 0;
    while (all(n, a))
    {
        half(n, a);
        counter++;
    }
    return counter;
}

int main()
{
    static int64_t a[200];
    int64_t n;
    scanf("%ld", &n);
    for (int64_t i = 0; i < n; ++i)
    {
        scanf("%ld", &a[i]);
    }
    printf("%ld\n", solver(n, a));
    return 0;
}

4問目 ABC087B - Coins

ループ ループ 3重ループ。
OCamlはfor for for。
Haskellでもfor for for。
Haskellではforを書けないとかのうわさはデマだし。

ABC087B.ml
(* ABC087B - Coins *)

let solver a b c x =
  let counter = ref 0 in
  for a' = 0 to a do
    for b' = 0 to b do
      for c' = 0 to c do
        if (500 * a') + (100 * b') + (50 * c') = x then counter := !counter + 1
      done
    done
  done;
  !counter

let () =
  let a = read_int () in
  let b = read_int () in
  let c = read_int () in
  let x = read_int () in
  solver a b c x |> string_of_int |> print_endline
ABC087B.hs
{- ABC087B - Coins -}
import Control.Monad.State.Strict
import Data.Foldable (forM_)

solver :: Int -> Int -> Int -> Int -> Int
solver a b c x =
  flip execState 0 $ do
    forM_ [0..a] $ \i -> do
      forM_ [0..b] $ \j -> do
        forM_ [0..c] $ \k -> do
          if 500*i + 100*j + 50*k == x then modify' succ else pure ()
    get

main :: IO ()
main = do
  a <- readLn :: IO Int
  b <- readLn :: IO Int
  c <- readLn :: IO Int
  x <- readLn :: IO Int
  putStrLn . show $ solver a b c x
ABC087B.c
/* ABC087B - Coins */
#include <stdio.h>
#include <stdint.h>

int32_t solver(int32_t A, int32_t B, int32_t C, int32_t X)
{
    int32_t counter = 0;
    for (int32_t a = 0; a <= A; ++a)
    {
        for (int32_t b = 0; b <= B; ++b)
        {
            for (int32_t c = 0; c <= C; ++c)
            {
                if ((500 * a) + (100 * b) + (50 * c) == X)
                {
                    ++counter;
                }
            }
        }
    }
    return counter;
}

int main()
{
    int32_t A, B, C, X;
    scanf("%d", &A);
    scanf("%d", &B);
    scanf("%d", &C);
    scanf("%d", &X);
    printf("%d\n", solver(A, B, C, X));
    return 0;
}

5問目 ABC083B - Some Sums

10で割れる限り, 10で割った余りをたし続ける。
OCamlは再帰で足し続ける。
Haskellはunfoldrで各桁のリストを作ってsumで総和を。

ABC083B.ml
(* ABC083B - Some Sums *)

let rec sum_of_digits x =
  if x >= 10 then (x mod 10) + sum_of_digits (x / 10) else x

let solver = function
  | [ n; a; b ] ->
      let test x = a <= x && x <= b in
      let summary = ref 0 in
      for n' = 1 to n do
        if test (sum_of_digits n') then summary := !summary + n'
      done;
      !summary
  | _ -> failwith "?"

let () =
  read_line ()
  |> Str.split (Str.regexp " ")
  |> List.map int_of_string |> solver |> string_of_int |> print_endline
ABC083B.hs
{- ABC083B - Some Sums -}
import           Data.List  (unfoldr)
import           Data.Maybe (Maybe (..))

sum_of_digits :: Int -> Int
sum_of_digits =
  sum . unfoldr f
  where
    f x | x == 0 = Nothing
        | otherwise = Just (x `mod` 10, x `div` 10)

solver :: [Int] -> Int
solver [n, a, b] =
  sum [x | x <- [1..n], let z = sum_of_digits x, a <= z && z <= b]

solver _ = 0

main :: IO ()
main = do
  xs <- map read . words <$> getLine
  putStrLn . show $ solver xs
ABC083B.c
/* ABC083B - Some Sums */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int32_t sum_of_digits(int32_t x)
{
    int32_t y = 0;
    for (; x; x /= 10)
        y += x % 10;
    return y;
}

int32_t solver(int32_t N, int32_t A, int32_t B)
{
    int32_t summary = 0;
    for (int32_t i = 1; i <= N; ++i)
    {
        int32_t s = sum_of_digits(i);
        if (A <= s && s <= B)
        {
            summary += i;
        }
    }
    return summary;
}

int main()
{
    int32_t N, A, B;
    scanf("%d %d %d", &N, &A, &B);
    printf("%d\n", solver(N, A, B));
    return 0;
}

6問目 ABC088B - Card Game for Two

大きい順にソートしてaliceとbobに配り, 足し合わせて差を求める。
OCamlはカウンタを見ながらそれぞれに配る。
Haskellはunfoldrでそれぞれに配る。

ABC088B.ml
(* ABC088B - Card Game for Two *)

let solver _ xs =
  let reversed = List.sort (fun x y -> compare y x) xs in
  let f (counter, alice, bob) x =
    if counter mod 2 = 0 then (succ counter, alice + x, bob)
    else (succ counter, alice, bob + x)
  in
  let counter, alice, bob = List.fold_left f (0, 0, 0) reversed in
  alice - bob

let () =
  let n = read_int () in
  read_line ()
  |> Str.split (Str.regexp " ")
  |> List.map int_of_string |> solver n |> string_of_int |> print_endline
ABC088B.hs
{- ABC088B - Card Game for Two -}
import           Data.List  (sortBy, unfoldr)
import           Data.Maybe (Maybe (..))

solver :: [Int] -> Int
solver xs =
  let reversed      = sortBy (\x y -> compare y x) xs
      (alice, bob)  = deal reversed
  in
  sum alice - sum bob
  where
    deal = unzip . unfoldr f
    f []         = Nothing
    f [a]        = Just ((a,0), [])
    f (a:b:rest) = Just ((a,b), rest)

main :: IO ()
main = do
  n <- readLn :: IO Int
  xs <- map read . words <$> getLine
  putStrLn . show $ solver xs
ABC088B.c
/* ABC088B - Card Game for Two */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int32_t compare(const void *x, const void *y)
{
    return *(int32_t *)y - *(int32_t *)x;
}

int32_t solver(int32_t N, int32_t *A)
{
    qsort(A, N, sizeof(int32_t), compare);
    int32_t alice = 0, bob = 0;
    for (int32_t i = 0; i < N; i += 2)
    {
        alice += A[i];
        bob += A[i + 1];
    }
    return alice - bob;
}

int main()
{
    static int32_t A[100];
    int32_t N;
    scanf("%d", &N);
    for (int32_t i = 0; i < N; ++i)
    {
        scanf("%d", &A[i]);
    }
    printf("%d\n", solver(N, A));
    return 0;
}

7問目 ABC085B - Kagami Mochi

重複を除いた数を数える。
元記事でstd::setを使っていたので
OCamlはSetを使うことにする。
HaskellはData.List.nubを使うことにする。
ライブラリを使うと一撃で終わる。

ABC085B.ml
(* ABC085B - Kagami Mochi *)

module IntSet = Set.Make (Int)

let solver stream =
  let rec go sets = function
    | Some s ->
      go
        (IntSet.add (int_of_string s) sets)
        (Stream.junk stream;
         Stream.peek stream)
    | None -> sets
  in
  go IntSet.empty (Stream.peek stream) |> IntSet.cardinal

let () =
  let n = read_int () in
  Stream.from (fun i -> if i < n then Some (read_line ()) else None)
  |> solver |> string_of_int |> print_endline
ABC085B.hs
{- ABC085B - Kagami Mochi -}
import           Control.Monad (replicateM)
import           Data.List     (nub)

solver :: [Int] -> Int
solver = length . nub

main :: IO ()
main = do
  n <- readLn :: IO Int
  xs <- replicateM n readLn
  putStrLn . show $ solver xs
ABC085B.c
/* ABC085B - Kagami Mochi */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int32_t compare(const void *x, const void *y)
{
    return *(int32_t *)x - *(int32_t *)y;
}

int32_t solver(int32_t N, int32_t *d)
{
    qsort(d, N, sizeof(int32_t), compare);
    int32_t counter = 1, prev = d[0];
    for (int32_t i = 0; i < N; ++i)
    {
        if (prev != d[i])
        {
            prev = d[i];
            ++counter;
        }
    }
    return counter;
}

int main()
{
    static int32_t d[100];
    int32_t N;
    scanf("%d", &N);
    for (int32_t i = 0; i < N; ++i)
    {
        scanf("%d", &d[i]);
    }
    printf("%d\n", solver(N, d));
    return 0;
}

8問目 ABC085C - Otoshidama

OCamlは例外でループ脱出はありだと思っている。 Haskellは特に必要ではないがデータ型宣言とクラスShowのインスタンスにしてみた。

ABC085C.ml
(* ABC085C - Otoshidama *)

exception Ok of (int * int * int)

let solver (n, y) =
  try
    for a = 0 to n do
      for b = 0 to n - a do
        let c = n - a - b in
        if (10000 * a) + (5000 * b) + (1000 * c) = y then raise (Ok (a, b, c))
      done
    done;
    (-1, -1, -1)
  with
  | Ok a -> a

let () =
  Scanf.sscanf (read_line ()) "%d %d" (fun n y -> (n, y)) |> solver
  |> fun (a, b, c) -> Printf.printf "%d %d %d\n" a b c
ABC085C.hs
{- ABC085C - Otoshidama -}
import qualified Data.ByteString.Char8 as B8
import           Data.Maybe            (fromMaybe, listToMaybe)

data ABC = ABC Int Int Int
instance Show ABC where
	show (ABC a b c) = show a ++ " " ++ show b ++ " " ++ show c

solver :: Int -> Int -> ABC
solver n y =
  fromMaybe notFound $ listToMaybe
    [ABC a b c | a <- [0..n], b <- [0..(n-a)], let c = n-a-b, cond a b c]
  where
    cond a b c = 10000*a + 5000*b + 1000*c == y
    notFound = ABC (-1) (-1) (-1)

main :: IO ()
main = do
  [n, y] <- map read . words <$> getLine
  putStrLn . show $ solver n y
ABC085C.c
/* ABC085C - Otoshidama */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

struct S
{
    int32_t a, b, c;
};

struct S solver(int32_t N, int32_t Y)
{
    struct S r;
    for (r.a = 0; r.a <= N; ++r.a)
    {
        for (r.b = 0; r.b <= (N - r.a); ++r.b)
        {
            r.c = N - r.a - r.b;
            if (10000 * r.a + 5000 * r.b + 1000 * r.c == Y)
            {
                return r;
            }
        }
    }
    r.a = r.b = r.c = -1;
    return r;
}

int main()
{
    int32_t N, Y;
    scanf("%d %d", &N, &Y);
    struct S s = solver(N, Y);
    printf("%d %d %d\n", s.a, s.b, s.c);
    return 0;
}

9問目 ABC049C - 白昼夢

元記事は後ろから見ているけど, 自分は正面突破した。
再帰的に見ながら最後までたどり着けたら”YES”ということで。

ABC049C.ml
(* ABC049C - 白昼夢 *)

let words = List.map Str.regexp [ "dream"; "dreamer"; "erase"; "eraser" ]

let solver str =
  let rec go = function
    | idx when idx = String.length str -> true
    | idx ->
      List.map
        (function
          | r when Str.string_match r str idx -> go (Str.match_end ())
          | _ -> false)
        words
      |> List.exists (( = ) true)
  in
  if go 0 then "YES" else "NO"

let () = read_line () |> solver |> print_endline
ABC049C.hs
{- ABC049C - Daydream -}
import qualified Data.ByteString.Char8 as B8
import           Prelude               hiding (words)

words :: [B8.ByteString]
words = map B8.pack [ "dream", "dreamer", "erase", "eraser" ]

solver :: B8.ByteString -> B8.ByteString
solver s =
  if go 0 then B8.pack "YES" else B8.pack "NO"
  where
    go idx | idx == B8.length s = True
    go idx =
      let
        s' = B8.drop idx s
        cond w = B8.isPrefixOf w s'
        nextIdx w = idx + B8.length w
      in
        any (== True) [if cond w then go (nextIdx w) else False | w <- words]

main :: IO ()
main = B8.putStrLn . solver =<< B8.getLine
ABC049C.c
/* ABC049C - Daydream */
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

bool walk(const char *s, const size_t length, size_t offset)
{
    static const char *words[4] = {"dream", "dreamer", "erase", "eraser"};

    if (length == offset)
        return true;
    else if (length < offset)
        return false;
    else
    {
        for (int32_t i = 0; i < 4; ++i)
        {
            size_t len = strlen(words[i]);
            if (strncmp(words[i], &s[offset], len) == 0)
            {
                if (walk(s, length, offset + len))
                    return true;
            }
        }
        return false;
    }
}

bool solver(char *s)
{
    return walk(s, strlen(s), 0);
}

int main()
{
    static char s[100001];
    gets(s);
    printf("%s\n", solver(s) ? "YES" : "NO");
    return 0;
}

10問目 ABC086C - Traveling

OCamlは例外でfold_leftの脱出はありだと思っている。
Haskellは遅延評価的な何かで実行のキャンセルが行われるんじゃないかな。

ABC086C.ml
(* ABC086C - Traveling *)

type coord = { x : int; y : int }

let distance a b = abs (a.x - b.x) + abs (a.y - b.y)

let solver (txy : (int * coord) array) =
  let zero = (0, { x = 0; y = 0 }) in
  let walk previous current =
    let dt = fst current - fst previous in
    let dist = distance (snd current) (snd previous) in
    if dt < dist then raise Not_found
    else if (dt - dist) mod 2 = 0 then current
    else raise Not_found
  in
  try Array.fold_left walk zero txy |> Fun.const "Yes" with Not_found -> "No"

let () =
  let n = read_int () in
  let init _ =
    Scanf.sscanf (read_line ()) "%d %d %d" (fun t x y -> (t, { x; y }))
  in
  Array.init n init |> solver |> print_endline
ABC086C.hs
{- ABC086C - Traveling -}
import qualified Data.ByteString.Char8 as B8
import           Data.Maybe            (Maybe (..), fromJust, fromMaybe,
                                        isNothing)

data Coord = MkCoord { cx :: Int, cy:: Int }

distance :: Coord -> Coord -> Int
distance a b = abs (cx a - cx b) + abs (cy a - cy b)

solver :: [(Int, Coord)] -> String
solver = yesOrNo . scanl walk zero
  where
    zero = Just (0, MkCoord 0 0)
    --
    walk Nothing _ = Nothing
    walk (Just previous) current =
      let
        dt = fst current - fst previous
        dist = distance (snd current) (snd previous)
      in
      if dt < dist then Nothing
      else if (dt - dist) `mod` 2 == 0 then (Just current)
      else Nothing
    --
    yesOrNo :: [(Maybe (Int, Coord))] -> String
    yesOrNo xs = if any isNothing xs then "No" else "Yes"

main :: IO ()
main = do
  _ <- readLn :: IO Int
  putStrLn . solver =<< map (fromJust . pack . B8.words) . B8.lines <$> B8.getContents
  where
    pack :: [B8.ByteString] -> Maybe (Int, Coord)
    pack [a,b,c] = do
      (t,_) <- B8.readInt a
      (x,_) <- B8.readInt b
      (y,_) <- B8.readInt c
      pure (t, MkCoord x y)
    pack _ = Nothing
ABC086C.c
/* ABC086C - Traveling */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>

#define abs(x) ((x) < 0 ? (-(x)) : (x))

struct Coord
{
    int32_t x, y;
};

int32_t distance(struct Coord a, struct Coord b)
{
    return abs(a.x - b.x) + abs(a.y - b.y);
}

struct TXY
{
    int32_t t;
    struct Coord c;
};

bool solver(int32_t N, struct TXY A[])
{
    struct TXY previous = {0};
    for (int32_t i = 0; i < N; ++i)
    {
        int32_t dt = A[i].t - previous.t;
        int32_t dist = distance(A[i].c, previous.c);
        if (dt < dist)
            return false;
        else if ((dt - dist) % 2 != 0)
            return false;
        previous = A[i];
    }
    return true;
}

int main()
{
    static struct TXY A[100000];
    int32_t N;
    scanf("%d", &N);
    for (int32_t i = 0; i < N; ++i)
    {
        scanf("%d %d %d", &A[i].t, &A[i].c.x, &A[i].c.y);
    }
    solver(N, A) ? puts("Yes") : puts("No");
    return 0;
}

まとめ

この3つの言語の速度は 最速がC言語それに一回り遅れてOCamlそれからHaskellと予想していたけど, 実行結果を見るとC言語とHaskellとOCamlはほぼ同着位のパフォーマンスを出すんだとわかった。

image01

image02