{-# title: AtCoder Beginners Selection を Haskell で -} # AtCoder Beginners Selection を Haskell で 書いた日: 2019 年 12 月 15 日、書いた人: [@unnohideyuki](https://twitter.com/unnohideyuki) ## はじめに これは、[Haskell Advent Calendar 2019](https://qiita.com/advent-calendar/2019/haskell) 15 日目の記事です。 [AtCoder](https://atcoder.jp/) は、オンラインで参加できるプログラミングコンテスト(競技プログラミング)のひとつです。 わたしも、この AtCoder に Haskell で参戦してみたいと思い、そのために必要そうなことがらを調べてみました。 まずは、AtCoder の入門用に選出された10個の過去問からなる [AtCoder Beginners Selection](https://atcoder.jp/contests/abs) (通称 ABS)を Haskell で書いていき、 その後、そこで用いた技術要素を紹介していくことにしたいと思います。 また、Haskell で競技プログラミングに参戦するにあたって心配していたのは、実行速度の問題と、 DP (Dynamic Programming) のように配列を更新しながら計算していく方法についてだったので、 それらについても少し触れます。 ## AtCoder Beginners Selection の各問題に対する回答例 いかに、ABS の 10 問それぞれについて、問題文へのリンクと Haskell で書いた回答例を示します。 ### 1. ABC086A - Product [ABC086A の問題文](https://atcoder.jp/contests/abs/tasks/abc086_a) $$
{
import           Control.Monad
import           Data.Maybe
import qualified Data.ByteString.Char8 as BS

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine

main = do
  [a, b] <- getIntList
  putStrLn $ ["Even", "Odd"] !! (a * b `mod` 2)
$$}

### 2. ABC081A - Placing Marbles

[ABC081A の問題文](https://atcoder.jp/contests/abs/tasks/abc081_a)

$$
{
import qualified Data.ByteString.Char8 as BS

main = do
  s <- BS.getLine
  print . BS.length . BS.filter (== '1') $ s
$$}

### 3. ABC081B - Shift only

[ABC081B の問題文](https://atcoder.jp/contests/abs/tasks/abc081_b)

$$
{
import           Control.Monad
import           Data.Bits
import qualified Data.ByteString.Char8 as BS
import           Data.List
import           Data.Maybe

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine

main = do
  n <- getInt
  as <- getIntList
  print . minimum . map f $ as
  where
    f x | x .&. 1 == 1 = 0
        | otherwise    = 1 + f (x `shiftR` 1)
$$}

### 4. ABC087B - Coins

[ABC087B の問題文](https://atcoder.jp/contests/abs/tasks/abc087_b)

$$
{
import           Control.Monad
import qualified Data.ByteString.Char8 as BS
import           Data.Maybe

readInt = fst . fromJust . BS.readInt
getInt = readInt <$> BS.getLine

main = do
  [a, b, c, x] <- replicateM 4 getInt
  print . length $
    [1 | i<-[0..a], j<-[0..b], k<-[0..c], 500*i + 100*j + 50*k == x]
$$}

### 5. ABC083B - Some Sums

[ABC083B の問題文](https://atcoder.jp/contests/abs/tasks/abc083_b)

$$
{
import           Control.Monad
import           Data.Maybe
import qualified Data.ByteString.Char8 as BS

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine

main = do
  [n, a, b] <- getIntList
  print $ sum [x | x <- [1..n], f x >= a, f x <= b]
  where f 0 = 0
        f x = x `mod` 10 + f (x `div` 10)
$$}

### 6. ABC088B - Card Game for Two

[ABC088B の問題文](https://atcoder.jp/contests/abs/tasks/abc088_b)

$$
{
import           Control.Monad
import           Data.List
import           Data.Maybe
import qualified Data.ByteString.Char8 as BS

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine

main = do
  _ <- getInt
  as <- getIntList
  print $ sum $ zipWith (*) (cycle [1, -1]) (sortBy (flip compare) as)
$$}

${sortBy (flip compare) as} の部分は、はじめ ${reverse $ sort as} と書いていたのですが、
すると hlint が、「reverse は避けよ」と書き換えを促してくれました。

### 7. ABC085B - Kagami Mochi

[ABC085B の問題文](https://atcoder.jp/contests/abs/tasks/abc085_b)

$$
{
import           Control.Monad
import           Data.Bits
import qualified Data.ByteString.Char8 as BS
import           Data.List
import           Data.Maybe

readInt = fst . fromJust . BS.readInt
getInt = readInt <$> BS.getLine

main = do
  n <- getInt
  ds <- replicateM n getInt
  print . length . group . sort $ ds
$$}

### 8. ABC085C - Otoshidama

[ABC085C の問題文](https://atcoder.jp/contests/abs/tasks/abc085_c)

$$
{
import           Control.Monad
import qualified Data.ByteString.Char8 as BS
import           Data.Maybe

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine

main = do
  [n, y] <- getIntList
  let a = [map show [a, b, c] | a <- [0..n], b <- [0..(n-a)],
           let c = n-a-b, 10000*a + 5000*b + 1000*c == y]
  if null a
    then putStrLn "-1 -1 -1"
    else putStrLn . unwords . head $ a
$$}

### 9. ABC049C - 白昼夢 / Daydream

[ABC049C の問題文](https://atcoder.jp/contests/abs/tasks/arc065_a)

$$
{
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Char8 as BS

main = do
  s <- BS.getLine
  putStrLn . solve $ BS.reverse s
  where
    solve s | s == "" = "YES"
            | BS.reverse "dream"   `BS.isPrefixOf` s = solve $ BS.drop 5 s
            | BS.reverse "dreamer" `BS.isPrefixOf` s = solve $ BS.drop 7 s
            | BS.reverse "erase"   `BS.isPrefixOf` s = solve $ BS.drop 5 s
            | BS.reverse "eraser"  `BS.isPrefixOf` s = solve $ BS.drop 6 s
            | otherwise = "NO"
$$}

### 10. ARC089A - Traveling

[ARC089A の問題文](https://atcoder.jp/contests/abs/tasks/arc089_a)

$$
{
import           Control.Monad
import qualified Data.ByteString.Char8 as BS
import           Data.Maybe

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine

main = do
  n <- getInt
  xs <- replicateM n getIntList
  putStrLn $ if solve ([0, 0, 0]:xs) then "Yes" else "No"
  where
    solve [a, b]   = reachable a b
    solve (a:b:xs) = reachable a b && solve (b:xs)
    reachable [t0, x0, y0] [t1, x1, y1] =
      t >= dist && (t - dist) `mod` 2 == 0
      where dist = abs (x1 - x0) + abs (y1- y0)
            t = t1 - t0
$$}

## 解説

10個の回答例は、いずれも短いプログラムなので、それぞれについて長々と説明する必要はないかなと思います。
いずれも ${main} 関数はかなり簡潔で、Haskell の記述力の高さを再認識しました。

あまり普通じゃない、というか、競技プログラミング向けかなと思うのが、入力に ${String} (${[Char]})
を一切使わなかったところでしょうか。
ABS では、時間制限にひっかかるような大きな入力はないので、普通に ${String} を用いても問題にならないのですが、
後のことを考えると、最初から ${String} を使わない入力関数群を用意しておいた方がいいです。

$$
{
{-# LANGUAGE OverloadedStrings #-}
import           Control.Monad
import           Data.Maybe
import qualified Data.ByteString.Char8 as BS

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine
$$}

使い方は回答例のなかにありますが、以下にまとめておきます。

### 整数をひとつ読む

整数をひとつ読み込むには、${getInt} を用います。

$$
{
  n <- getInt
$$}

### 空白で区切られた複数の整数を読む

一行に空白でくぎられた複数の整数がならんでいる場合には、${getIntList} を使います。
以下のようにパターンマッチと組み合わせるのが便利ですね。

$$
{
  [a, b] <- getIntList
$$}

### 複数行から読み込む

複数行からなる入力を読み込む際には、${replicateM} を使います。

$$
{
  xs <- replicateM n getInt
$$}

[10. ARC089A - Traveling](https://uhideyuki.sakura.ne.jp/studs/index.cgi/ja/AbsInHaskell#p13) の回答例にあるように、
${replicateM m getIntList} のようにすれば、リストのリストとなります。

## もう少し進んだ例1: Vector を使おう

これまでの例では、${String} が遅いので使うのをやめましょうということを述べてきたわけですが、
${String} が遅いというのは、要するに ${List} が遅いからなので、
大量に読みこむ場合には、${Vector} を使うべきです。

以下は、[ABC147D](https://atcoder.jp/contests/abc147/tasks/abc147_d) の回答例:

$$
{
import           Control.Monad
import           Data.Array.IO
import           Data.Bits
import qualified Data.ByteString.Char8       as BS
import           Data.Char
import           Data.Maybe
import qualified Data.Vector.Unboxed         as V
import qualified Data.Vector.Unboxed.Mutable as VM

readInt = fst . fromJust . BS.readInt
getInt = readInt <$> BS.getLine
getIntVec n = V.unfoldrN n (BS.readInt . BS.dropWhile isSpace) <$> BS.getLine

main = do
  n <- getInt
  as <- getIntVec n

  cs <- VM.new 61
  VM.set cs (0::Int)

  forM_ [0..n-1] $ \i -> do
    let a = as V.! i
    forM_ [0..60] $ \j ->
      when (testBit a j) $ do
        c <- VM.read cs j
        VM.write cs j (c+1)
  cs' <- V.freeze cs

  let calc i c a = let c1 = fromIntegral c :: Integer
                       c2 = fromIntegral (n-c) :: Integer
                   in
                     c1 * c2 * bit i + a

  print $ V.ifoldr calc 0 cs' `mod` 1000000007
$$}

この問題では、最大 \(3 \times 10^5\) 個の整数が並ぶことになるので、そういった場合のために
${getIntVec} を用意しました。

これさえあれば、${getIntList} は不要かというと、そうでもなくて、${[a, b] <- getIntList}
のような使い方が便利なので、両方使い分けるのがいいと思います。

また、この例は、mutable な Vector の使用例にもなっています。

${ifoldr} というのも、私は今回初めてつかいました。fold 関数のなかで、その要素に対応する添え字 i が
使えるという、便利なしろもの。

## もう少し進んだ例2: Array も使おう

Mutable な Vector があれば、一次元 dp の類はOKなのですが、AtCoder には多次元 dp も頻出します。
そこで、Array を使います。

以下は、少し古い問題ですが、[ABC012D](https://atcoder.jp/contests/abc012/tasks/abc012_4)
の回答例です。

$$
{
import           Control.Exception     (assert)
import           Control.Monad
import qualified Control.Monad.ST      as ST
import qualified Data.Array.IO         as IO
import qualified Data.Array.ST         as ST
import qualified Data.Array.Unboxed    as A
import qualified Data.ByteString.Char8 as BS
import           Data.List
import           Data.Maybe

readInt = fst . fromJust . BS.readInt
readIntList = map readInt . BS.words
getInt = readInt <$> BS.getLine
getIntList = readIntList <$> BS.getLine

warshallFloyd :: A.UArray (Int, Int) Int -> A.UArray (Int, Int) Int
warshallFloyd iarr = oarr
  where
    l  = (fst . fst . A.bounds) iarr
    l' = (snd . fst . A.bounds) iarr
    r  = (fst . snd . A.bounds) iarr
    r' = (snd . snd . A.bounds) iarr
    oarr = assert (l==l' && r==r') $ ST.runSTUArray $ do
      d <- ST.thaw iarr
      forM_ [l..r] $ \k ->
        forM_ [l..r] $ \i ->
          forM_ [l..r] $ \j -> do
            dik <- ST.readArray d (i,k)
            dkj <- ST.readArray d (k,j)
            dij <- ST.readArray d (i,j)
            when (dik + dkj < dij) $
              ST.writeArray d (i,j) (dik + dkj)
      return d

main = do
  let infty = 10^6 :: Int
  [n, m] <- getIntList

  arr <- IO.newArray ((1,1),(n,n)) infty :: IO (IO.IOUArray (Int, Int) Int)
  forM_ [1..n] $ \i -> IO.writeArray arr (i,i) 0
  replicateM_ m $ do
    [a, b, t] <- getIntList
    IO.writeArray arr (a, b) t
    IO.writeArray arr (b, a) t

  edges <- IO.freeze arr
  let d = warshallFloyd edges
  print $ minimum [maximum [d A.! (i, j) | j<-[1..n]] | i<-[1..n]]
$$}

Mutable なデータの参照・更新には、${IO} や ${ST} (両者を含む共通クラスが ${PrimMonad})
を用いるのですが、上の例には両方の使用例が含まれます。

あと、最後の行なんかみると、やっぱりリストは便利だなぁ(Vector の方が速いからと、完全のリストを禁じてにはできないなぁ)
という気がしますね。

## さいごに

なんか、すごく駆け足になってしまいましたが、AtCoder に Haskell で参戦するために最低限しっておきたかった事柄に
ついて、一通りご紹介してみました。もうちょっと、丁寧に説明してみたかった気もしますが、
下手な説明よりも動くコードがあれば十分なのかもしれない。

ところで、私は、今年の8月に AtCoder 初参戦以来 13 回参加してきたのですが、実はずっと C# で書いてました。
別に C# 使いだったわけではなくて、C# にも少し興味があったので AtCoder を始めるときに C# を選んだという…。
もちろん、そのときに Haskell も試したんですが、そのときは、ちょっと無理かなと思ってしまって。

でも、今回アドベントカレンダーにむけて少し調べた結果、これなら Haskell でもいけそうかなぁという気になってきました。
それに、やっぱり C# よりも Haskell の方が好きかも。
次から Haskell で参戦します!

なお、今回の調べものにあたっては、以下のサイトを参考にさせていただきました。ありがとうございました。

- [HaskellでAtCoderの問題を解く(入力の高速化編)](https://qiita.com/hnw/items/3f7d27b742c5a1a99a9a)
- [Haskellで解くAtCoder - The curse of λ](https://myuon.github.io/posts/haskell-atcoder/)

以上です!