以下の内容はhttps://torus711.hatenablog.com/entry/2025/08/31/223440より取得しました。


Haskell で AtCoder ABC421 A-D

はじめに

 テンプレは別記事

A

 問題文 : https://atcoder.jp/contests/abc421/tasks/abc421_a
 言われた通りに,文字列リストの $x$ 番目の要素が文字列 $y$ と等しいか否かを判定します.型が異なる $x, y$ が同じ行で与えられるせいで,入力をどう扱うかの方が面倒かもしれません.今回は,どちらも String で受け取っておいて使うときになってから read する実装にしました.

main = do
	n <- readInt
	ss <- replicateM n readStr
	[ x, y ] <- readStrs
	putStrLn $ yesno $ ss !! ( pred $ read x ) == y

B

 問題文 : https://atcoder.jp/contests/abc421/tasks/abc421_b
 Fibonacci 数列で手前 $2$ 項を足す替わりに手前 $2$ 項を足してから文字列として reverse したものを数として読んだものを次項とする数列の第 $10$ 項を求める問題です.
 Haskell で Fibonacci 数列といえば,

as = 1 : 1 : zipWith (+) as ( tail as )

という有名なイディオムがあります.このイディオムにおける (+) を「足してから文字列として reverse したものを数として読む」関数に置き換えれば問題を解けます.
 足すのは当然 (+) として,「文字列として reverse して数として読む」部分は read . reverse . show で実装できます.あとはこれらを合成したいですが,(+) が $2$ 引数関数なので read . reverse . show . (+) としてしまうと型が合わず合成できません.ぐぐれば出てくるので詳細は割愛しますが,ここではまず read . reverse . show(.) の部分適用を取り出して ( ( read . reverse . show ) . ) を作り,これと (+) を合成して ( ( ( read . reverse . show ) . ) . (+) ) とすることで型が合います.
 あとは,$10$ 番目の要素を読めば問題が解けます.

main = do
	[ x, y ] <- readInts
	let
		as = x : y : zipWith ( ( ( read . reverse . show ) . ) . (+) ) as ( tail as )
	print $ as !! 9

C

 問題文 : https://atcoder.jp/contests/abc421/tasks/abc421_c
 条件を満たす文字列は ABABA...BABAB... のいずれかです.また,同じ文字同士の相対位置を逆転させる意味はありません.なので,作る文字列を固定すると各文字の行き先も定まります.一回の操作で行き先までの距離が合計 $2$ 縮まるので,行き先までの距離の総和の半分が,その文字列を作るコストです.よって,$2$ つの目標についてコストを計算して大きくない方をとればそれが答えです.
 $2$ 種の目標をできるだけ統一的に扱いたい気がしてきますが,AB を入れ替えた文字列に対して同じアルゴリズムを適用すればよいです.
 各文字の行き先を計算するには,AB の出現回数をもちながら文字列を先頭から舐めればよいです.

main = do
	readStr
	s <- readStr
	let
		s' = map ( bool 'A' 'B' . ( == 'A' ) ) s
	print $ min ( solve s ) ( solve s' )

f _ _ [] = []
f a b (c:s)
	| c == 'A' = a * 2 : f ( succ a ) b s
	| otherwise = 1 + b * 2 : f a ( succ b ) s

solve = ( `div` 2 ) . sum . map abs . zipWith (-) [ 0 .. ] . f 0 0

D

 問題文 : https://atcoder.jp/contests/abc421/tasks/abc421_d
 方向と距離からなるリスト $2$ つをパラレルに処理します.リストの先頭にある要素たちを見て,移動距離が大きくない方の距離分の移動を処理して,残り距離が $0$ になったら pop するというようにします.
 交わる回数についてはユーザ解説様が全てという気がしますが,

  • 同じ方向に動くなら,始点が同じならずっと一緒.
  • そうでないなら,$1$ ステップで Manhattan 距離 が $2$ 変化するので交わる「かもしれない」座標が計算可能で,そこに届く移動幅があれば交わる.

ということになります.
 あとは,計算に必要な色々な値を計算するコードを書けば問題を解けます.

main = do
	[ y1, x1, y2, x2 ] <- readInts
	[ _, m, l ] <- readInts
	ps <- uncurry ( zipWith (,) ) . ( map head . head &&& map read . last ) . transpose <$> replicateM m readStrs
	qs <- uncurry ( zipWith (,) ) . ( map head . head &&& map read . last ) . transpose <$> replicateM l readStrs
	print $ solve y1 x1 y2 x2 ps qs

solve _ _ _ _ [] [] = 0
solve y1 x1 y2 x2 ( ( d1, a ):rest1 ) ( ( d2, b ):rest2 )
	| d1 == d2 = solve' + if ( y1, x1 ) == ( y2, x2 )
		then dist
		else 0
	| odd md || md == 0 = solve'
	| otherwise = solve' + if ( y1 + dy1 * md `div` 2, x1 + dx1 * md `div` 2 ) == ( y2 + dy2 * md `div` 2, x2 + dx2 * md `div` 2 ) && md `div` 2 <= dist 
		then 1
		else 0
	where
	md = abs ( y1 - y2 ) + abs ( x1 - x2 )
	dist = min a b
	directions = zip "RDLU" $ zip [ 0, 1, 0, -1 ] [ 1, 0, -1, 0 ]
	( dy1, dx1 ) = fromJust $ lookup d1 directions
	( dy2, dx2 ) = fromJust $ lookup d2 directions
	ny1 = y1 + dy1 * dist
	nx1 = x1 + dx1 * dist
	ny2 = y2 + dy2 * dist
	nx2 = x2 + dx2 * dist
	ps = if a == dist then rest1 else ( d1, a - dist ):rest1
	qs = if b == dist then rest2 else ( d2, b - dist ):rest2
	solve' = solve ny1 nx1 ny2 nx2 ps qs



以上の内容はhttps://torus711.hatenablog.com/entry/2025/08/31/223440より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14