🌳

[ABC350 D] New Friends

2024/04/24に公開

https://atcoder.jp/contests/abc350/tasks/abc350_d

完全グラフの特徴を学ぶ問題。

前提知識

完全グラフの辺の数を求める式 n * (n - 1) / 2 はどこから来たのか?

まず、

  • n 個から r 個選ぶのは nCr
  • nCr は nPr / r! のこと
  • r! は r から 2 までカウントダウンしながら掛け算
  • nPr は n から r 回カウントダウンしながら掛け算

したがって、たとえば 4 個から 2 個選ぶには 4P2 / 2! で、

4 * 3 / 2  # => 6

になる。

それはいったん置いといて、nCr を使ってグラフ上の辺を作るため2つの頂点を選ぶとすれば、r は 2 固定になる。そうすると nPr は本来 (n - 0) * (n - 1) * (n - 2) * (n - 3) …… と続くけど r = 2 なら、(n - 0) * (n - 1) までの固定になる。そこに仮で頂点数を n = 4 としてみると、

n = 4
(n - 0) * (n - 1) / 2  # => 6
(4 - 0) * (4 - 1) / 2  # => 6
4 * 3 / 2              # => 6

となって最初の式と一致する。だから完全グラフの辺の数は n * (n - 1) / 2 で求まる。

参照

問題を要約する

  • A-B-C の関係の A-C を友達にしたい
  • 全体で何件可能か?

考え方

  • 完全グラフの辺の数 - 現状の辺の数 = 友達にできる数

これをすべての連結成分に対して行う。その連結成分を知るために素集合森を作る。

3頂点でのシミュレーション

頂点 A B C を用意して、

可能な限り辺を貼った状態が、

完全グラフの 3 本になる。

その 3 本は、頂点数 n = 3 から n * (n - 1) / 2 で求まる。

3 * (3 - 1) / 2  # => 3

一方、現状が、

2 本だったすれば、

  • 理想は3本 - 今は2本 → 残り1本

で、あと1ヶ所友達にできる。

この例は、頂点数 3 に対して完全グラフの辺の数も 3 になってわかりづらい。

例1のシミュレーション

頂点 1 2 3 4 を用意して、

可能な限り辺を貼った状態が、

完全グラフの 6 本になる。

その 6 本は、頂点数 n = 4 から n * (n - 1) / 2 で求まる。

n = 4
n * (n - 1) / 2  # => 6

一方、現状は、

3 本なので、

  • 理想は6本 - 今は3本 → 残り3本

で、あと3ヶ所友達にできる。

解説の模範解答風 (AC)

N, M = gets.split.collect(&:to_i)                         # => [5, 3]
AB = M.times.collect { gets.split.collect(&:to_i) }       # => [[1, 2], [2, 3], [1, 4]]
ds = DisjointSet.new(1..N).unites(AB)                     # => 親:{1=>2, 2=>2, 3=>2, 4=>2, 5=>5} 構図:{2=>[1, 2, 3, 4], 5=>[5]} 節数:{2=>4} 木数:2 高さ:{2=>2}
sum = ds.groups.sum { |_, g| g.size * (g.size - 1) / 2 }  # => 6
p sum - M                                                 # => 3

現状の辺の数は各「森の大きさ - 1」なので、

ds.groups.sum { |_, g| g.size - 1 }  # => 3

でもいいが、遡れば M のことなので最後に引くのは M でいい。

M  # => 3

なお、sum を求める部分は、

sum = (1..N).sum { |e| ds.size(e) - 1 } / 2  # => 6

として「頂点ごとに所属する木の大きさ - 1」の合計の半分でも求まるようだが、どういう考え方なのかはよくわからない。

参考

Discussion