# ABC392 振り返り
- [日本レジストリサービス(JPRS)プログラミングコンテスト2025#1(AtCoder Beginner Contest 392) - AtCoder](https://atcoder.jp/contests/abc392)
- ABCD 4完 (1083) ··· [コンテスト成績証 - AtCoder](https://atcoder.jp/users/oceajigger/history/share/abc392)
![[スクリーンショット 2025-02-09 8.46.04.png]]
- A (灰, 11) ··· 順列全探索
- B (灰, 29) ··· Set 系の `difference` を使うと楽
- C (灰, 132) ··· 人の関係を有向グラフにし、人 → ゼッケン、ゼッケン → 人を引けるように配列を二つ用意
- D (茶, 579) ··· おちついて確率を計算する。全探索で OK
- E (水, 1218, upsolve) ··· Union-Find で冗長な辺を割り出す。連結成分が $k$ 個あったら $k - 1$ 個の冗長辺を使ってそれらを結ぶ
- F (水, 1395, upsolve) ··· `Data.Sequence`の `insertAt` で瞬殺
でした。
E の冗長辺を結ぶ処理で WA が一つでてしまい、それを取るのに必死になって F をほとんどみないままゲームオーバー
F はよくみたら Haskell だとスーパー Easy な問題で、もったいなかったです
## [A - Shuffled Equation](https://atcoder.jp/contests/abc392/tasks/abc392_a)
3変数なので全パターンを列挙してもいいですが `permutations` を使うのが楽
```haskell
main :: IO ()
main = do
as <- getInts
let res = [b1 * b2 == b3 | [b1, b2, b3] <- permutations as]
printYn $ or res
```
## [B - Who is Missing?](https://atcoder.jp/contests/abc392/tasks/abc392_b)
$1 \ldots N$ に含まれない数を出す、というのは Set 系の `difference` を使うと簡単。欠けた数字を割り出すのによく使うイディオムです。
```haskell
main :: IO ()
main = do
[n, m] <- getInts
as <- getInts
let diff = IS.difference (IS.fromList [1 .. n]) (IS.fromList as)
print $ IS.size diff
printList $ IS.toList diff
```
## [C - Bib](https://atcoder.jp/contests/abc392/tasks/abc392_c)
問題文を読み解くのが面倒な問題。
人の関係は有向グラフで結ばれているグラフ (Functional Graph) として表現すると楽
あとはゼッケンの番号を割り出す方法が欲しい。人 → ゼッケン、ゼッケン → 人の両方の検索が必要なので配列を正方向、逆方向で用意してあとは問題文通りに実装。
```haskell
main :: IO ()
main = do
n <- getInt
ps <- getInts
qs <- getInts
let qa = listArray @UArray (1, n) qs
invQ = array @UArray (1, n) $ zip qs [1 :: Int ..]
g = graph (1, n) $ indexed 1 ps
res = [qa ! y | i <- [1 .. n], let x = invQ ! i, let y = head $ g ! x]
printList res
```
## [D - Doubles](https://atcoder.jp/contests/abc392/tasks/abc392_d)
例えば $(1, 2, 3)$ というサイコロと、$(1, 2, 2, 1)$ というサイコロがあったとすると
- 出目 $1$ が被る確率 ··· 一つ目のサイコロで $1$ が出る確率は $\frac{1}{3}$ 、二つ目では $\frac{2}{4}$、 この二つが同時に出現する確率なので積で結び $\frac{1}{3} \times \frac{2}{4} = \frac{2}{12}$
- 出目 $2$ が被る確率 ··· 同様に計算して $\frac{1}{12}$
- 出目 $3$ は一方にしかないので出ない ··· 確率 $0$
となり、出目 $1$ と出目 $2$ の出現はそれぞれ独立なので和で結合し $\frac{2}{12} + \frac{2}{12} = \frac{4}{12} = \frac{1}{3}$
と計算できます。
必要な部品としては
- 2 つのサイコロを比較するにあたり、それぞれのサイコロの出目の出現頻度
- 2 つのサイコロを比較するにあたり、両方に存在する出目のリスト (一方にしかない出目は計算対象にしなくていい)
です。前者はバケットをとり、後者は集合の `intersection` を使えばよいでしょう。
というわけで、各サイコロをバケットと 集合に変換しておき、`combintations 2` で全ての組み合わせを全探索し先の容量で確率を計算する。
$N \leq 100$ なので `combinations 2` は高々 $4950$ 通り、サイコロの出目の長さは全体を足しても $10^5$ 程度なので割と愚直でもいけます。
```haskell
main :: IO ()
main = do
n <- getInt
items <- replicateM n $ do
k : as <- getInts
return (k, as)
let items' =
[ (k, bucket, s)
| (k, as) <- items,
let bucket = toBucket (1, 10 ^ 5) as
s = IS.fromList as
]
res =
[ sum' [(fromIntegral (bucket1 ! i) / fromIntegral k1) * (fromIntegral (bucket2 ! i) / fromIntegral k2) | i <- IS.toList s]
| ((k1, bucket1, s1), (k2, bucket2, s2)) <- comb2 items',
let s = IS.intersection s1 s2
]
print $ maximum res
```
## [E - Cables and Servers](https://atcoder.jp/contests/abc392/tasks/abc392_e) (upsolve)
Union-Find で、空のグラフを用意しそこに辺を継ぎ足していく。ある辺を加えるとき、その両端が既に連結している場合はそれを冗長な辺と判断し、脇に寄せておく。
これで余りの辺がわかる。
ところで、グラフ全体を連結にするには先の Union-Find で出来上がった連結成分同士を繋げればよい。
できあがった連結成分の数を $k$ とすると、冗長な辺を $k - 1$ 本繋ぎ直すことでグラフ全体を連結にすることができる。
つまり、最小の操作回数は必ず $k - 1$ になる。
冗長な辺からどれでもいいので $k - 1$ 本をチョイスし、それをまだ連結できてない別の連結成分の代表元に繋ぎなおす。
これでいけます。
繋ぎ直しの処理をどうするかですが、$k - 1$ 本の辺のリストと代表元のリストを同時に再帰で回し、辺の一端 $u$ と代表元 $r$ がまだ連結してなければその二者を連結する。
もし連結している場合は、辺は消費せずに $r$ を代表元リストの一番後ろに回して次の代表元をみる。
この再帰でかならず $k - 1$ 本は消費されるはずなので、辺のリストが空になるまで、やる。
本番ではこの繋ぎ直しの処理の記述を微妙に間違えていて、 WA 1 個が取れず結局時間オーバーになってしまいました。無念
```haskell
main :: IO ()
main = do
[n, m] <- getInts
uvs <- replicateM m getTuple
uf <- newUF @IOUArray (1, n) (-1)
edges <- for (indexed 1 uvs) $ \(i, (u, v)) -> do
same <- isSame uf u v
if same
then do
return $ Just (u, i)
else do
unite uf u v
return Nothing
vss <- getComponentsUF uf
roots <- for vss $ \vs -> do
rootUF uf (head vs)
let k = length roots
result <- loop uf (take (k - 1) $ catMaybes edges) roots []
print (k - 1)
for_ result $ \(i, u, v) -> do
printList [i, u, v]
loop _ [] _ acc = return acc
loop _ _ [] acc = return acc
loop uf ((e@(u, i) : es)) (r : rs) acc = do
same <- isSame uf u r
if not same
then do
unite uf u r
loop uf es rs ((i, u, r) : acc)
else loop uf (e : es) (rs `snoc` r) acc
```
## [F - Insert](https://atcoder.jp/contests/abc392/tasks/abc392_f) (upsolve)
並び順を保証されているデータ列の任意の位置に数を追加していく。
初期状態でデータ列は空とみなすことができ、挿入回数は $O(10^5)$ 程度。
Haskell なら Data.Sequence に `insertAt` という位置指定でシーケンスに値を挿入できる API があり、計算量は挿入位置が $i$ でデータサイズが $n$ のとき $𝑂(log(min(𝑖,𝑛−𝑖)))$ 程度です。なので、これを使うだけで解ける。
E の WA 1 個をとるのに執着してしまい、F がまさかこんな瞬殺問題だとは思わず。もったいない!
Data.Sequence がない場合は双方向リストとか、セグ木や BIT を使って解けばよさそうです。
```haskell
main :: IO ()
main = do
_ <- getInt
ps <- getInts
let q' = foldl' f Seq.empty (indexed 1 ps)
where
f q (i, p) = Seq.insertAt (p - 1) i q
printList $ toList q'
```