gt; readArray dist u) (g ! v) queue' <- foldForM queue us $ \q (u, w) -> do writeArray dist u $ fmap (+ w) d return $ q |> u aux queue' main :: IO () main = do [n, m] <- getInts uvs <- replicateM m getWeightedEdge let g = wGraph (1, n) $ concat [[((u, v), w), ((v, u), -w)] | ((u, v), w) <- uvs] dist <- newArray @IOArray (1, n) Nothing for_ [1 .. n] $ \v -> do bfs' g dist v xs <- getElems dist printList $ catMaybes xs ``` コンテスト中、BFS の実装を終えたところでグラフが非連結であることを考慮しておらず、まずいなと思いました。 Haskell でこういう大域的なデータ構造を持ち回りながら副作用を起こして何かするみたいなのを、その場で焦って書こうとすると沼りやすい。 どうしようかなーと思いましたが、解法と実装方針は分かっていたので ChatGPT にそれを指示して Python でコードを出力させて AC しました。 ### 重み付き Union-Find ところで、この問題は重み付き Union-Find を貼るだけでも解けます。 これは本番中にはまったく気づかず。 「重み付き Union-Find」は差分制約 / 相対情報を扱うことができるデータ構造で、この問題のように各要素に決められた値が不明な状態で、要素間の差分がわかるときに出番です。 今回の問題では入力に矛盾がないことが保証されていますが、重み付きUnion-Find は矛盾がある場合も扱うことができます。 [ABC328 F - Good Set Query](https://atcoder.jp/contests/abc328/tasks/abc328_f) を upsolve するために実装した重み付き Union-Find の実装を貼ってみたところ、確かに貼るだけで解けました。 グラフ通りに結合して、結果定まった各要素の重みを出力するだけ。 慣れている人にとってはおそらくこれが最速でしょう。 ```haskell main :: IO () main = do [n, m] <- getInts uvs <- replicateM m getWeightedEdge uf <- newWUF @IOUArray (1, n) (-1 :: Int) for_ uvs $ \((u, v), w) -> do uniteWUF uf u v w ws <- traverse (getWeightWUF uf) [1 .. n] printList ws {-- 重み付き Union-Find --} data WeightedUnionFind a v = WeightedUnionFind (a v v) -- 親頂点 / -1 は代表元 (IOUArray v Int) -- 集合サイズ (代表元で検索する) (IOUArray v Int) -- weight (IORef Int) -- 連結成分数 v -- 代表元 (representative element) newWUF :: (MArray a v IO, Ix v) => (v, v) -> v -> IO (WeightedUnionFind a v) newWUF (l, u) rep = WeightedUnionFind <gt; newArray (l, u) rep <*> newArray (l, u) 1 <*> newArray (l, u) 0 <*> newIORef (bool 0 (ix u + 1 - ix l) (u >= l)) <*> pure rep where ix = index (l, u) rootWUF :: (MArray a v IO, Ix v) => WeightedUnionFind a v -> v -> IO v rootWUF uf@(WeightedUnionFind parent _ weight _ rep) x = do p <- readArray parent x if p == rep then return x else do r <- rootWUF uf p writeArray parent x r -- 累積和 w <- readArray weight p updateArray (+) weight x w return r getWeightWUF :: (MArray a v IO, Ix v) => WeightedUnionFind a v -> v -> IO Int getWeightWUF uf@(WeightedUnionFind _ _ weight _ _) x = do _ <- rootWUF uf x -- 経路圧縮 readArray weight x -- x と y のグループを W_y - W_x = d (W_y = W_x + d) となるように統合する uniteWUF :: (MArray a v IO, Ix v) => WeightedUnionFind a v -> v -> v -> Int -> IO () uniteWUF uf@(WeightedUnionFind parent size weight refN _) x y w = do x' <- rootWUF uf x y' <- rootWUF uf y wx <- getWeightWUF uf x wy <- getWeightWUF uf y let w' = w + wx - wy when (x' /= y') $ do sizeX <- readArray size x' sizeY <- readArray size y' -- 併合する毎に集合が一つ減る modifyIORef' refN (+ (-1)) if sizeX > sizeY then do writeArray size x' (sizeX + sizeY) writeArray parent y' x' writeArray weight y' w' else do -- swap writeArray size y' (sizeX + sizeY) writeArray parent x' y' writeArray weight x' (negate w') isSameWUF :: (MArray a v IO, Ix v) => WeightedUnionFind a v -> v -> v -> IO Bool isSameWUF uf x y = (==) <gt; rootWUF uf x <*> rootWUF uf y ``` ## 感想など A 〜 D まで簡単な問題が多く、早解きできただけという感じなので、パフォーマンスはそこそこ良かったものの不完全燃焼感がちょっとあります。 まあ、こういうこともときおりありますね。 D が重み付き Union-Find だとすぐ気づけたとか、大域的にデータを用意した DFS / BFS の実装のテンプレートをもっていればあと5 〜 10分程度短縮できたかなと思うと やや悔しいところではあります。 レートは 1084 となって、2連勝。その前の3連敗分を少し戻しましたがまだ Highest までは戻っていません。 次回も勝ちたいところ!