以下の内容はhttps://torus711.hatenablog.com/entry/2025/05/02/202739より取得しました。


Haskell で AtCoder ABC 403 A-D

はじめに

 テンプレは別記事

A

 問題文 : https://atcoder.jp/contests/abc403/tasks/abc403_a
 リストの奇数番目の要素の総和を求める問題です.奇数番目の要素を抜き出すのは地味に面倒そうですが,面白いイディオムがあります.まず,cycle [ True, False ] で $\mathrm{True}$ と $\mathrm{False}$ が交互に出現する無限リストを作れます.このリストを filter id すると(ある種の Toy Problem ですが)奇数番目の要素だけを取り出していることになります.よって,このリストを入力のリストと zip して filter することで,入力のリストの奇数番目の要素だけを取り出すことができます.
 あとは map で入力のリストに対応する方を取り出して sum すれば答えが求まります.

main = getLine >> readInts >>= print . sum . map snd . filter fst . zip ( cycle [ True, False ] )

B

 問題文 : https://atcoder.jp/contests/abc403/tasks/abc403_b
 入力が小さく $T$ から部分文字列を切り出す開始位置を全て試しても間に合うので,そのようにします.始点を全て試すのは tails でできますが,長さが $|U|$ 未満のものを取ってしまうと後々困るので,$|S| - |U| + 1$ 個を take します.
 $T$ の部分文字列の $i$ 番目の文字が $c_1$ で $U$ の $i$ 番目の文字が $c_2$ だったとき.$c_1 = \text{'?'}$ または $c_1 = c_2$ のときマッチさせることができます.従って,この条件で zipWith して and することで,この部分で全てマッチさせられるかを判定できます(長さ $|U|$ 未満の部分文字列を取ってしまうとここでミスる).
 あとは $T$ の各部分文字列について上記の判定をして or すれば問題を解けます.

main = do
	t <- getLine
	u <- getLine
	putStrLn $ yesno $ or $ do
		s <- take ( length t - length u + 1 ) $ tails t
		return $ and $ zipWith f s u

f '?' _ = True
f c1 c2 = c1 == c2

C

 問題文 : https://atcoder.jp/contests/abc403/tasks/abc403_c
 $\text{ ヒト } \times \text{ コンテスト } \rightarrow \{ \mathrm{ True }, \mathrm{ False } \}$ な写像を二次元配列で表すと MLE するので,別の方法で情報をもちます.まずヒト $i$ がコンテスト $j$ を読める状態を順序対 $( i, j )$ で表現することにします.クエリ $1$ で与えられる情報はこれで表現できます.クエリ $2$ を同じ方法で表現しようとするとデータの数が多くなってしまって TLE/MLE するので,ヒト $i$ が全てのコンテストを読める状態を順序対 $( i, 0 )$ で表現することにします.これらの情報を Data.Set で管理します.
 こうすると,クエリ $3$ に対しては $( x, y )$ または $( x, 0 )$ が Set 内にあるかどうかを調べることで全てのクエリを $O( \log q )$ 時間で処理できます.
 実装としては,Set とクエリを受け取って,そのクエリを処理した後の Set と出力値(に対応する Bool)を返すような関数を作ると mapAccumL で畳み込むことができます.ただし,出力はクエリ $3$ にしか無いので,クエリ $1, 2$ に対する出力は Nothing で表し,クエリ $3$ の出力に対応する真偽値を Just に入れて表現します.呼び出し側では.catMaybesJust 値だけ集めてから文字列に変換する関数を map することで望むものが得られます.

main = readInts >>= flip replicateM readInts . last >>= mapM_ putStrLn . map yesno . catMaybes . snd . mapAccumL solve Set.empty

solve set [ 1, x, y ] = ( Set.insert ( x, y ) set, Nothing )
solve set [ 2, x ] = ( Set.insert ( x, 0 ) set, Nothing )
solve set [ 3, x, y ] = ( set, Just $ Set.member ( x, 0 ) set || Set.member ( x, y ) set )

D

 問題文 : https://atcoder.jp/contests/abc403/tasks/abc403_d
 $d = 0$ の場合は $A$ から重複無く要素を最大限取り出せばよいので,$n$ から $A$ のユニーク要素数を引いた値が答えになります.ユニーク要素数は,素朴に(かつ一般的に)やるなら sort + group でユニーク要素を一つのリストに固めた二重リストを作れるので,更に length をとると求まります.今回の場合は値が Int なので,一旦 Data.IntSet に全部突っ込んでから size をとることで定数倍速い*1実装になります.
 $d \neq 0$ のケースについて考えます.この場合は $d$ での剰余が異なる値同士は干渉しないので,剰余毎に独立に最適値を求めた和が答えです.剰余が等しい値を昇順に並べることを考えると,この列挙において連続する値を共に残すことはできません.このコンセプトに従って残すか否かを昇順に選ぶと考えると,
\[
\mathit{ dp }_{ i, j } = \text{$i$ 個分の扱いを決定済み.次の要素について,$j = 1$ iff 次の値は消さなければならない}
\]
という風に状態を取る DP が設計できます.DP はかなりミュータブルなアルゴリズムなので,DP テーブルは ST モナドの中で STUArray を使います.
 DP の中で各値を残さないことにした場合の答えへの寄与は,その値の $A$ での出現回数です.個数を数えるには,accumArray (+) 0 で一旦配列にしてから elems でリストに戻すと線形時間で処理できます.$d$ での剰余毎に分類するには,一旦 Data.List.Extra にある chunksOf d で $d$ 個毎に区切ってから transpose で転置すればよいです.

main = do
	[ n, d ] <- readInts
	as <- readInts
	let
		cs = transpose $ chunksOf d $ elems $ accumArray (+) 0 ( 0, 10^6 ) $ zip as ( repeat 1 )
	print $ if d == 0
		then n - ISet.size ( ISet.fromList as )
		else sum $ map solve cs

solve cs = runST $ do
	dp <- newArray ( ( 0, 0 ), ( n, 1 ) ) ( maxBound `div` 2 ) :: ST s ( STUArray s ( Int, Int ) Int )
	-- ( # of considered, force to remove next ) -> min cost
	writeArray dp ( 0, 0 ) 0
	writeArray dp ( 0, 1 ) 0
	forM_ ( zip [ 0 .. ] cs ) $ \( i, c ) -> do
		forM_ [ 0, 1 ] $ \j -> do
			modifyArray dp ( i + 1, 0 ) =<< min . ( + c ) <$> readArray dp ( i, j )
			when ( j == 0 ) $ do
				modifyArray dp ( i + 1, 1 ) =<< min <$> readArray dp ( i, j )
	min <$> readArray dp ( n, 0 ) <*> readArray dp ( n, 1 )
	where
	n = length cs

*1:少なくとも今回の問題への提出結果によれば.




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

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