AtCoder Beginner Contest 405(Promotion of AtCoder Career Design DAY)に参加したので振り返り。
A - Is it rated?
まずDiv.1/2で場合分けし、次にレーティングが範囲内か否かを判定する。
main :: IO ()
= do
main <- int2
(r,x)
$ case x of
printYn 1 -> 1600 <= r && r <= 2999
2 -> 1200 <= r && r <= 2399
B - Not All
問題文の通り、後ろから1つずつ削除しながら、条件を満たすか否かをチェックすればよい。
flip fix
を使うと雑にループを書けて便利。
main :: IO ()
= do
main <- int2
(n,m) <- list1 ucInt
as
let result = flip fix (0,reverse as) $ \loop (acc,bs) ->
case bs of
-> acc
[] -> if any (`notElem` bs) [1..m]
_ then acc
else loop (acc+1,tail bs)
print result
foldl'
等を使用した実装は思い浮かばなかった。。。
あとは明示的に再帰を書くくらいしかできないかな?
go :: Int -> Int -> [Int] -> Int
= acc
go _ acc [] @(_:bs) = if any (`notElem` as) [1..m]
go m acc asthen acc
else go m (acc+1) bs
C - Sum of Product
\(S_i = \sum_{j=i}^N A_j\) とおけば、
\[\begin{align*} \sum_{1 \le i < j \le N} A_i A_j &= A_1 \sum_{j=2}^N A_j + A_2 \sum_{j=3}^N A_j + \dotsb + A_{N-1}A_N& \\ &= \sum_{i=1}^{N-1} A_i S_{i+1} & \end{align*} \]\(S_i\) は scanr1
で簡単に計算できる。
main :: IO ()
= do
main <- val ucInt
n <- list1 ucInt
as
let si = L.scanr1 (+) as
print $ sum $ zipWith (*) as $ tail si
D - Escape Route
まず、各 \((i,j)\) について E
からの最短距離をBFSで求める。
各点から距離を1ずつ減らす向きに進むと最寄りの E
に到達するので、進む向きに応じて \((i,j)\) に ^v<>
をおけばよい。
コンテスト中は、進む向きに応じて ^v<>
をおく処理で良い感じの実装が思いつかず、可変配列を使って手続き的にゴリゴリと書いて時間がかかった。
あと、 \((i,j)\) の上下左右4点を列挙する処理 nei4
をライブラリとして用意していたが、これにバグがありちょっと躓いた。
処理の中で、ある点と周囲の点の座標を明示的に計算していてごちゃごちゃしてしまった。。。 以下のようなデータ型を用意しておくと、実装が簡単になりそう。
data Direction = L | R | U | D
deriving (Eq, Bounded, Enum)
allDirections :: [Direction]
= [minBound..maxBound]
allDirections
d2c :: Direction -> Char
L = '<'
d2c R = '>'
d2c U = '^'
d2c D = 'v'
d2c
d2i :: Direction -> (Int, Int)
L = (0,-1)
d2i R = (0,1)
d2i U = (-1,0)
d2i D = (1,0)
d2i
(^+) :: (Int, Int) -> (Int, Int) -> (Int, Int)
^+ (u, v) = (i+u, j+v) (i, j)
また、矢印の向きは点の座標から導出できるので、可変配列を使用する必要もない。
上記を踏まえ、書き直したものが以下。
main :: IO ()
= do
main <- int2
(h,w) <- charGrid h w
grid
let exits = map fst $ filter (\(_,e) -> e == 'E') $ assocs grid
= ((1,1),(h,w))
bnd
= runSTUArray $ bfs (nexts ((/='#').(grid!)) bnd nei4) bnd exits
dist = array @UArray bnd [(ij, f ij) | ij <- range bnd]
result where f ij = case grid ! ij of
'.' -> d2c . head . concat . flip map allDirections $
-> [d | let ij' = ij ^+ d2ij d, inRange bnd ij', dist ! ij - dist ! ij' == 1]
\d -> x
x
printGrid result
E - Fruit Lineup
残り20分くらいで取り掛かり始めたが、クリティカルな考察にはたどり着かず時間切れ。
一番左にあるブドウを境に、列を左右に分ける。 「リンゴはすべて、ブドウよりも左側に並べる」「オレンジはすべて、ブドウよりも左側に並べる」という条件から、 左にはすべてのリンゴとオレンジが存在する。 また左に存在するバナナの個数を \(x \ (0 \le x \le C)\) 個とすると、 残りの「リンゴはすべて、バナナよりも左側に並べる」という条件から、 左の並べ方の総数は、「🍎🍎🍎🍎🍎🍎🍎🍎🍌🍌🍌🍌🍌🍌🍌🍌🍌」のように並べたリンゴとバナナの間にオレンジを挿入する場合の数となり、これは \(\dbinom{A+x+B}{B}\) に等しい。
また右には、 \(C-x\) 個のバナナと \(D-1\) 個のブドウが存在する。 ブドウとバナナの並べ方に条件はないため、並べ方の総数は \(\dbinom{C-x+D-1}{D-1}\) となる。 したがって、並べ方の総数は、各 \(x\) について和をとった
\[\begin{equation*} \sum_{x=0}^C \dbinom{A+x+B}{B}\dbinom{C-x+D-1}{D-1} \end{equation*} \]となる。
実装は以下を参考にした (前処理なしで計算したらサンプル問題から計算が終わらなかった)。
競プロでよく使う二項係数(nCk)を素数(p)で割った余りの計算と逆元のまとめ
main :: IO ()
= do
main <- val $ (,,,) <$> ucInt <*> ucInt <*> ucInt <*> ucInt
(a,b,c,d)
let tbl = initNCK (a+b+c+d)
= nCkMod tbl (a + x + b) b * nCkMod tbl (c - x + d - 1) (d - 1)
f x
print $ sum $ map f [0..c]
data NCKTable =
NCK { size :: Int
factTable :: VU.Vector IntMod
, invTable :: VU.Vector IntMod
, factInvTable :: VU.Vector IntMod
,
}
initNCK :: Int -> NCKTable
= let ft = VU.constructN n $ \vec ->
initNCK n case VU.length vec of
0 -> 1
1 -> 1
-> IntMod x * vec VU.! (x - 1)
x = VU.constructN n $ \vec ->
it case VU.length vec of
0 -> 1
1 -> 1
-> - vec VU.! (modulus `mod` x) * IntMod (modulus `div` x)
x = VU.constructN n $ \vec ->
fit case VU.length vec of
0 -> 1
1 -> 1
-> vec VU.! (x - 1) * it VU.! x
x in NCK n ft it fit
nCkMod :: NCKTable -> Int -> Int -> IntMod
NCK size factTable _ factInvTable) n k
nCkMod (| size < n || size < k = error "size over"
| n < k = error "invalid k"
| otherwise = factTable VU.! n * factInvTable VU.! k * factInvTable VU.! (n-k)
感想
方針が立っても実装がうまく行かなくて解けない、といった場面が自分にはよく見られるが、Dはごちゃつきながらもなんとか通せたのは良かったかな。
とはいえ、今回のDはそれほど難しい問題でもなかったので、さくっと実装したいところ。単純にパターンが自分に染み込んでない。
いつもながらの振り返りだけど、もっと精進しようね、と。
そういえば
https://x.com/drken1215/status/1919710467066626284
ABC 212〜317 の A〜F を、思考を自動化できるレベルまで練習を繰り返すことが良いそうです!!!
たとえば、C 問題を 2 分で解けとまでは言わないけど、10 分かかってるようだと何かしら身に付いていない可能性があって、そのままではいけないと言っていました
https://x.com/drken1215/status/1919746815135506631
りんごさん曰く、同じ問題を 3 回解き直すくらいは当たり前みたいです。
そのくらいやらないと、「思考の自動化」はできて来ないと。
https://x.com/drken1215/status/1919703191857295621
これ一応、正確には
・ABC 212〜317 の A〜F を完全にマスターすれば、 ・黄色になれる
という設計みたいです。
よーしがんばるぞ