# ABC376 振り返り - [AtCoder Beginner Contest 376(Promotion of AtCoder Career Design DAY) - AtCoder](https://atcoder.jp/contests/abc376) (バーチャル参加) - ABCDE 5完 (1299) ![[スクリーンショット 2024-10-22 18.45.45.png]] ABC376 本戦は欠席。バーチャル参加でやりました。 5完でした。 - A (灰) ··· 前回飴をもらった時刻を状態として持って、前回からの差が $C$ 以上になった回数を数える - B (灰) ··· 実装問題。右周り、左回りの二通り $\times$ `L` を動かす `R` を動かす $\times$ 間に一方の手がないかどうか、の $8$ 分岐を丁寧にやる - C (灰) ··· 大きいおもちゃから貪欲に、十分な大きさの箱を当てていく。multiset 相当使って実装 - D (茶) ··· 頂点 $1$ の隣接頂点から BFS して頂点 $1$ までの最短距離を求める - E (緑) ··· $(A_i, B_i)$ を $A_i$ の昇順にソートし、$A_i$ を固定してその $A_i$ を最大値とみなし $B_i$ の top K (最小 K 個) を保持しながら計算していく 実装的には 5問中 4問で `mapAccumL` を使いました。 以下、軽く振り返ります。 ## [A - Candy Button](https://atcoder.jp/contests/abc376/tasks/abc376_a) 前回飴をもらってから $C$ 秒経過すると飴が貰える。 前回もらったときを状態として、差が $C$ 以上開いた回数を数える。 ```haskell main :: IO () main = do [n, c] <- getInts ts <- getInts let (_, res) = mapAccumL f (-10 ^ 18) ts where f prev t | t - prev >= c = (t, 1) | otherwise = (prev, 0) print $ sum' res ``` ## [B - Hands on Ring (Easy)](https://atcoder.jp/contests/abc376/tasks/abc376_b) 考察は容易いが、実装がややこしい。分岐を丁寧にやる。 `L` と `R` の位置を状態として保持しながら - `L` と `R` どちらを動かすのか - 左回りに動かすか、右回りに動かすか - 道中に反対の手があるか、ないか をうまく分岐する。 ```haskell main :: IO () main = do [n, q] <- getInts qs <- replicateM q $ auto @(Char, Int) let res = mapAccumL f (1, 2) qs where f (l, r) ('L', ti) | ti > l = ((ti, r), bool d (n - d) (inRange (l, ti) r)) | otherwise = ((ti, r), bool d (n - d) (inRange (ti, l) r)) where d = abs (ti - l) f (l, r) ('R', ti) | ti > r = ((l, ti), bool d (n - d) (inRange (r, ti) l)) | otherwise = ((l, ti), bool d (n - d) (inRange (ti, r) l)) where d = abs (ti - r) f _ _ = error "Invalid" print $ sum' $ snd res ``` ### 追記 [ABC338 D - Island Tour](https://atcoder.jp/contests/abc338/tasks/abc338_d) などでもやったように、進行方向を常に一方向にする、ということをすれば、もう少し簡単になった。 ```haskell main :: IO () main = do [n, q] <- getInts qs <- replicateM q $ auto @(Char, Int) let res = mapAccumL f (1, 2) qs where f (l, r) ('L', ti) = ((ti, r), bool d (n - d) (inRange (from, to) r)) where d = abs (ti - l) (from, to) = if l < ti then (l, ti) else (ti, l) f (l, r) ('R', ti)= ((l, ti), bool d (n - d) (inRange (from, to) l)) where d = abs (ti - r) (from, to) = if r < ti then (r, ti) else (ti, r) f _ _ = error "Invalid" print $ sum' $ snd res ``` ## [C - Prepare Another Box](https://atcoder.jp/contests/abc376/tasks/abc376_c) 最後に残したいのはなるべく小さなおもちゃ。 なので、大きなものから貪欲に片付けていく。このとき、必要以上に大きな箱は使わないようにしたい。 $A_i$ を降順にみていき、各 $A_i$ にその時点で残っているベストマッチな $B_i$ を探して当てていく IntMultiSet を使って実装すると、ベストマッチを探したり、すでに使った $B_i$ 取り除くのも含めて操作が楽。 ```haskell main :: IO () main = do n <- getInt as <- getInts bs <- getInts let (_, res) = mapAccumL f (fromListMS bs) (sortOn' Down as) where f s ai = case lookupGEMS ai s of Just bi -> (deleteMS bi s, Nothing) Nothing -> (s, Just ai) res' = catMaybes res if length res' == 1 then print $ head res' else print (-1 :: Int) ``` ## [D - Cycle](https://atcoder.jp/contests/abc376/tasks/abc376_d) 強連結成分分解··· といきたいところだがそんなもので殴る必要もなく、頂点 $1$ の隣接頂点から一斉に出発して BFS し、$1$ に辿り着くまでの最短経路を割り出せばよい。 閉路になっていなければ $1$ には辿り着けない。 ```haskell main :: IO () main = do [n, m] <- getInts uvs <- replicateM m getTuple let g = graph (1, n) uvs dist = bfs (g !) (-1) (bounds g) [(x, 1) | x <- g ! 1] print $ dist ! 1 ``` ## [E - Max × Sum](https://atcoder.jp/contests/abc376/tasks/abc376_e) 説明するとややこしいのでコードをみるほうがはやそう。 $A_i$ の小さなものから固定していきたい。$(A_i, B_i)$ を $A_i$ の昇順にソートする。 この整列済みの数列を、$A_i$ の小さな方から、$A_i$ を固定していみていく。 固定された $A_i$ が $max \space A_i$ だとすると、数列 $B = (B_1,B_2, \ldots, B_i)$ までの中から $K$ 個が、その $A_i$ にとってベストな部分集合ということになる。 ここに気がつけるかどうかが問われる問題。 あとは実装。最小値 top K 個を管理しながら、集合にどんどん値を追加していくような操作ができれば数列 $B$ の表現が簡単。 これは [ABC281 E - Least Elements](https://atcoder.jp/contests/abc281/tasks/abc281_e) や [ABC306 E - Best Performances](https://atcoder.jp/contests/abc306/tasks/abc306_e) でも題材になった、top K の値とその top K の総和を動的に管理できる集合データ構造を使うと良い。 自分は BalancedMultiSet という名前でライブラリ化している。 これを使えば実装は瞬殺である。 ```haskell main :: IO () main = do t <- getInt replicateM_ t $ do [_, k] <- getInts as <- getInts bs <- getInts let (_, res) = mapAccumL f (emptyBMS k LT) $ sortOn fst $ zip as bs where f s (a, b) = let s' = insertBMS b s in (s', a * leftSum s') print $ minimum $ drop (k - 1) res {-- BalancedMultiSet --} data BalancedMultiSet = BalancedMultiSet { topKValue :: Int, topKOrder :: Ordering, left :: IntMultiSet, right :: IntMultiSet, leftSum :: Int } deriving (Show) emptyBMS :: Int -> Ordering -> BalancedMultiSet emptyBMS k order = BalancedMultiSet { topKValue = k, topKOrder = order, left = emptyMS, right = emptyMS, leftSum = 0 } nullBMS :: BalancedMultiSet -> Bool nullBMS BalancedMultiSet {..} = nullMS left singletonBMS :: Int -> Ordering -> Int -> BalancedMultiSet singletonBMS k order x = insertBMS x (emptyBMS k order) fromListBMS :: Int -> Ordering -> [Int] -> BalancedMultiSet fromListBMS k order = foldl' (flip insertBMS) (emptyBMS k order) insertBMS :: Int -> BalancedMultiSet -> BalancedMultiSet insertBMS x s@BalancedMultiSet {..} | sizeMS left < topKValue = balanceBMS (s {left = insertMS x left, leftSum = leftSum + x}) | otherwise = balanceBMS (s {right = insertMS x right}) deleteBMS :: Int -> BalancedMultiSet -> BalancedMultiSet deleteBMS x s@BalancedMultiSet {left, right, leftSum} | memberMS x left = balanceBMS (s {left = deleteMS x left, leftSum = leftSum - x}) | otherwise = balanceBMS (s {right = deleteMS x right}) balanceBMS :: BalancedMultiSet -> BalancedMultiSet balanceBMS s@BalancedMultiSet {..} | (not . nullMS) right && sizeMS left < topKValue = balanceBMS ( s { left = insertMS q left, right = deleteMS q right, leftSum = leftSum + q } ) | p `cmp` q = s | otherwise = balanceBMS ( s { left = insertMS q (deleteMS p left), right = insertMS p (deleteMS q right), leftSum = leftSum + q - p } ) where cmp = case topKOrder of LT -> (<=) GT -> (>=) EQ -> error "" (p, q) = case topKOrder of LT -> ( bool maxBound (findMaxMS left) (not . nullMS $ left), bool maxBound (findMinMS right) (not . nullMS $ right) ) GT -> ( bool minBound (findMinMS left) (not . nullMS $ left), bool minBound (findMaxMS right) (not . nullMS $ right) ) EQ -> error "Unsupported ordering" ```