はじめに
テンプレは別記事.
A
問題文 : https://atcoder.jp/contests/abc422/tasks/abc422_a
『スーパーマリオブラザーズ』みたいなステージ番号が与えられるので,次のステージのステージ番号を求める問題です.
次ステージのステージ番号は問題文の通りに条件分岐すればよいので,入力が i-j という形式で与えられることの方が面倒かもしれません.幸い Haskell は遅延評価なので,map digitToInt <$> getLine としてしまっても digitToInt '-' の部分を評価しなければ問題無く実行できます.
出力の形式は入力と同じ形式ですが,それぞれ show して concat するより Text.Printf にある printf の方が楽な気がします.uncurry すれば引数をタプルで渡せるので,右側に素直に if を書けてよい感じです.
main = do [ i, _, j ] <- map digitToInt <$> getLine uncurry ( printf "%d-%d\n" ) if j == 8 then ( i + 1, 1 ) else ( i, j + 1 )
B
問題文 : https://atcoder.jp/contests/abc422/tasks/abc422_b
., '#' からなる二次元の盤面が与えられるので,'#' であって $2$ 個または $4$ 個の # と隣接しているセルを数える問題です.
二次元の盤面に添え字アクセスするなら二重リストより配列になっていてくれた方が好みなので,入力の段階で listArray を fmap しておきます.
数える部分はリストの do で二重ループ的なことをして普通に数えます.$4$ 近傍を見るには zip [ 0, 1, 0, -1 ] [ 1, 0, -1, 0 ] から値を取り出す do*1を書くと添字を簡単に作れます.近傍を参照するときは (!?) を使うと範囲外なら Nothing を,範囲内ならそこの値を Just に入れて返してくれるのですっきりします.が,今の AtCoder 環境だと「そんなん無いよ」と言われてしまうので,自分で定義したものをテンプレに入れています.
main = do [ h, w ] <- readInts ss <- listArray ( ( 1, 1 ), ( h, w ) ) . concat <$> replicateM h getLine putStrLn $ yesno $ and do i <- [ 1 .. h ] j <- [ 1 .. w ] guard $ ss ! ( i, j ) == '#' return $ ( `elem` [ 2, 4 ] ) $ length do ( di, dj ) <- zip [ 0, 1, 0, -1 ] [ 1, 0, -1, 0 ] guard $ ss !? ( i + di, j + dj ) == Just '#' return ()
C
問題文 : https://atcoder.jp/contests/abc422/tasks/abc422_c
文字 A, B, C をいくつかずつ持っていて,A?C の形で表せる*2文字列を最大いくつ作れるかという問題です.
本番では C++ で二分法をしたのですが,若干面倒です.解説によれば,答えは $\min\left\{ a, b, \left\lfloor \frac { a + b + c } 3 \right\rfloor \right\}$ なので,これなら「書くだけ」です.
main = readInt >>= flip replicateM do [ a, b, c ] <- readInts print $ minimum [ a, c, ( a + b + c ) `div` 3 ]
D
問題文 : https://atcoder.jp/contests/abc422/tasks/abc422_d
(概要割愛)
ざっくりと言えば,列を真ん中で分けたときに $k$ を左右に均等に分配すればよく,再帰的に同じことが言えます.また,長さ $1$ の列は $k$ のみからなるシングルトンです.なので,作るべき列の長さ $n$ と $k$ の値を受け取って適当に再帰する関数を書けば実装できます.
アンバランスさの値を $0$ にできることは $k \bmod 2^k = 0$ と同値で,$0$ でないなら $1$ になります.
main = do [ n, k ] <- readInts print $ if k `mod` 2^n == 0 then 0 else 1 printList $ solve (2^n) k solve 1 k = [k] solve n k = let n' = n `div` 2; k' = k `div` 2 in solve n' k' ++ solve n' ( k - k' )
E
問題文 : https://atcoder.jp/contests/abc422/tasks/abc422_e
奇数個の点の集合が与えられて,過半数の点が載る直線が存在するかを求める問題です.
そういう直線が存在するなら,(過半数という条件から)適当に $2$ 点選んだとき $\frac 1 4$ の確率で直線に載ります.逆に言えば $\frac 3 4$ の確率で載らないわけですが,$2$ 点のピックを $k$ 回やれば,(そういう直線が存在するとした上で)一度も載らない確率は $\left( \frac 3 4 \right)^k$ です.$k = 50$ とか適当に大きくとれば全外しする確率は十分小さくなるので,乱択アルゴリズムにより十分高い確率で直線を(あれば)発見できます.
ということで乱数を使いたいのですが,滅多に使わないのでー,ええとー……,System.Random ですね.randomRs で与えた範囲に含まれる疑似乱数の無限リストを作れるので,ここから take して使います.また,範囲の指定は $7$ 要素のタプルまでは対応しているっぽいのであまり困らず使えそうです.
選ばれた $2$ 点を通る直線の一般形を求める数学パートは割愛します.
$2$ 点の各ピックについて,過半数の条件を満たしたら一般形のパラメータを Just に入れて返し,そうでなければ Nothing を返すようにしておいて catMaybes するようにすると見通しがよいかもしれません.
main = do n <- readInt [ xs, ys ] <- map ( listArray ( 1, n ) ) . transpose <$> replicateM n readInts let res = catMaybes $ take 50 do ( i, j ) <- randomRs ( ( 1, 1 ), ( n, n ) ) ( mkStdGen 711 ) guard $ i /= j let ( a, b, c ) = if | xs ! i == xs ! j -> ( 1, 0, -( xs ! i ) ) | ys ! i == ys ! j -> ( 0, 1, -( ys ! i ) ) | otherwise -> ( ys ! j - ys ! i, -( xs ! j - xs ! i ), -( a * xs ! i + b * ys ! i ) ) cnt = length do ( x, y ) <- zip ( elems xs ) ( elems ys ) guard $ a * x + y * b + c == 0 return () return $ if n <= 2 * cnt then Just [ a, b, c ] else Nothing if null res then putStrLn "No" else do putStrLn "Yes" printList $ head $ res