{-# 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/) 以上です!