gt; getInts let as = listArray @UArray (1, n - 1) $ zipWith (-) (tail as_) as_ secs = shakutori (1, n - 1) (as !) (+) (-) 0 (\acc r -> acc + r < m) ans = succ . maximumWithDefault 0 $ map (\(l, r) -> r + 1 - l) secs print ans ``` ---- ## [D - ABC Puzzle](https://atcoder.jp/contests/abc326/tasks/abc326_d) (upsolve) 問題の制約からして、全パターンを構成して判定することを匂わせます。 コンテスト中にはうまい探索方法が見つけられず、断念しました。 条件を満たすグリッドにおいては - `A` は各行各列に必ず 1 回ずつ出てくる。つまり、`AAAAA` という5つの A をそれぞれ何行目に置くかという順列 $5!$ パターンを考えれば良い - `B` と `C` も同様 というわけで、実は $5! = 120$ の $120^3 = 1728000$ で $10^6$ なので全部試せますよ、というところに気がつけるかどうかでした。 `A` `B` `C` の出現位置を全部生成しても計算可能だということがわかれば、あとはただ実装するだけ。`permutations` で `A` `B` `C` それぞれの出現位置を全列挙して、`sequence` でその直積集合を得ます。直積の元を `accumArray` でまとめ上げれば $120^3$ 個のグリッドの出来上がりです。 あとはグリッドから、$R$ と $C$ と比較するための文字列を抜き取って判定です。 ```haskell toGrid n = accumArray @UArray (flip const) '.' ((1, 1), (n, n)) rowHeads grid = do let rows = chunksOf n (elems grid) map (head . filter (/= '.')) rows where (_, (_, n)) = bounds grid colHeads grid = do let cols = transpose $ chunksOf n (elems grid) map (head . filter (/= '.')) cols where (_, (_, n)) = bounds grid isOk r c grid | any (\row -> length (filter (/= '.') row) /= 3) (chunksOf n $ elems grid) = False | rowHeads grid /= r = False | colHeads grid /= c = False | otherwise = True where (_, (_, n)) = bounds grid main :: IO () main = do n <- getInt r <- getLine c <- getLine let as = [map (,'A') (zip [1 ..] xs) | xs <- permutations [1 .. n]] bs = [map (,'B') (zip [1 ..] xs) | xs <- permutations [1 .. n]] cs = [map (,'C') (zip [1 ..] xs) | xs <- permutations [1 .. n]] xss = sequence [as, bs, cs] let gs = filter (isOk r c) $ map (toGrid n . concat) xss if null gs then putStrLn "No" else do putStrLn "Yes" mapM_ putStrLn $ chunksOf n (elems $ head gs) ``` コンテスト中は「重実装っぽいなー、これは罠」と思ってましたが、終わってみれば、事前の正確な計算量見積もりができればスマートなやり方が見つかる良問だった気がします。 ---- ## [E - Revenge of "The Salary of AtCoder Inc."](https://atcoder.jp/contests/abc326/tasks/abc326_e) (upsolve) 出ました期待値DP。確率DP、期待値DP はよく出ますね。 確率・期待値DP は水色の登竜門だということがわかってきました。 今回の問題は [[ABC263 E - Sugoroku 3]] 同様に、期待値DP をただするだけでは難しいので、セグメント木や Binary Indexed Tree などで区間和を高速に取得できると良い問題です。 DP の構成の仕方は前からやる、後ろからやる、どちらでも良さそうですが私は後ろからやりました。 - $dp(x)$ を「現在が $x$ のとき、そこからダイスを降ってもらえる金額の期待値」とする - $dp(n) = 0$ ··· $x = n$ のときはダイスをそこから振っても必ずゲームが終了するため - この $dp(n)= 0$ を足場に、DP を開始する。つまり $n$ から $0$ の方に進んで答えが $dp(0)$ になるパターン - $dp(i)$ からみると、次のトライでは $A_{i + 1} \ldots A_n$ までの値を確率 $\frac{1}{N}$ でもらえる。これが次のトライで手に入る給料の期待値 - 加えて、それまでの累積期待値 $dp(i + 1) \ldots dp(n)$ も乗ってくる と考えます。実装としては - $A_{i + 1} \ldots A{n}$ を毎回計算する必要があるのであらかじめ $A$ の累積和をとっておく - $dp(i + 1) \ldots dp(n)$ は動的な区間和なのでセグメント木や Binary Indexed Tree を使うと楽。 としました。 区間和でいいなら BIT の方が簡単に使えるので、BIT を選択しました。 ```haskell main :: IO () main = do n <- getInt as <- getInts let s = listArray @UArray (1, n + 1) $ scanl' (+) 0 as dp <- newBIT @IOArray (0, n) forM_ [n - 1, n - 2 .. 0] $ \i -> do let a = s ! (n + 1) - s ! (i + 1) b <- rangeSumBIT dp (i + 1) (n + 1) let x = (IntMod a + b) * invIntMod (IntMod n) incrementBIT dp i x IntMod ans <- rangeSumBIT dp 0 1 -- dp(0) print ans ``` 確率・期待値DP は、2週間ほど前に過去問を $10$ 問ほどやってはいたものの、本番中には解けず。過去問を $1$ 回ずつやった程度では、まだ身体化できてないことがよくわかります。 計算の構造にはなんとなく気づけたものの、いざ実装してみると手が動かなかったです。 期待値DPや確率DP にはある程度パターンがあるので、まずはそれを身体化するところまで持っていきたいです。コモディティな部分はパターン認識で済ませて認知エネルギーを節約できれば、問題の本質的なところに思考を集中させることができます。 ---- ## 全体を通しての感想 今週は木曜日に Qiita カンファレンスがあり、その発表の準備などもあってあまり精進できていなかったので、本戦に響かないかどうか心配でしたが杞憂でした。 冒頭にも書いた通り、簡単な問題への対応力はおかげさまで安定力が増してきている感があるので、改めて確率・期待値DP など水色問題に着手していこうと思います。ただし、まめにバチャに出るなどして、適正難易度の問題に対する感覚を維持することも忘れないよう > 自分