🌳

[ABC120 D] Decayed Bridges

2024/01/27に公開

https://atcoder.jp/contests/abc120/tasks/abc120_d

森を作りながら「木の大きさ×木の大きさ」を求める問題。

前提知識

N 個の組み合わせは何通り?

N * (N - 1) / 2 で求まるらしいのだが、どこかで習った記憶がまるでない。

自力で計算してみると、

N 計算過程 X通り
5 4 + 3 + 2 + 1 10
4 3 + 2 + 1 6
3 2 + 1 3
2 1 1
1 0 0

となるが、巷の公式を使うと、たしかに一回で求まる。

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

X個とY個の組み合わせは何通り?

これは難しく考えてはいけない。9 個と 9 個の組み合わせであれば 9 * 9 = 81 通りである。

ことわざ

  • 橋を見たら素集合データ構造と思え

言葉の置き換え

  • 橋の崩落 → 辺の削除
  • 島 → 木

すべてを反転する

素集合データ構造は削除が苦手なので、

  • 橋を削除するごと不便になっていく

を、

  • 橋を作成するごと便利になっていく

に置き換える。こんな発想知ってないと出てこない。具体的には辺のリストを逆から参照していく。注意点としてループ内の処理も逆になる。言葉にすると余計ややこしくなりそうだが「削除した後で不便さを調べる」のではなく「作成する前に便利さを調べる」になる。

例1のシミュレーション

1ターン目

最初はすべての橋がないので便利さ 0

2ターン目

1 と 4 を併合して「1の大きさ(1) * 4の大きさ(1) = 1」だけ便利になり、0 + 1 で 1

3ターン目

同様に 2 と 3 を併合して「2の大きさ(1) * 3の大きさ(1) = 1」だけ便利になったので 1 + 1 で 2

4ターン目

続いて 1 と 3 を併合して「1の大きさ(2) * 3の大きさ(2) = 4」だけ便利になったので 2 + 4 で 6。この時点で便利さは最大になっている。

5ターン目

3 と 4 はもう併合しているので便利さに変化なく 6

元の世界線に戻す

ここで「便利さ順」を「不便さ逆順」に変換する。

不便さの最大は 6

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

なので、便利さの値

a = [0, 1, 2, 6, 6]

を「不便さの最大 - 便利さ」で反転させ、

a = a.collect { |e| max - e }  # => [6, 5, 4, 0, 0]

この並びを逆にすれば答えになる。

a.reverse  # => [0, 0, 4, 5, 6]

ショートカットな考え方

「不便さの最大」は最初からわかる (N からわかる) ので「不便さの最大」から「便利さ」を少しずつ引いていく方法もある。その方がショートカットできるし、だいたいの人がそうしている。ただ、すべてを逆にして考えているのに、不便さが介入してくるのに違和感がある。「削除」と「不便」はない世界線で考えたい。したがって便利さを求めてから不便さに変換するようにした。

コード (AC)

N, M = gets.split.collect(&:to_i)                    # => [4, 5]
AB = M.times.collect { gets.split.collect(&:to_i) }  # => [[1, 2], [3, 4], [1, 3], [2, 3], [1, 4]]

v = 0                                               # 便利さ
av = []
ds = DisjointSet.new
AB.reverse.each do |a, b|
  av << v
  if ds.different?(a, b)         # a, b が併合されていなかったら a, b をこのあと併合することで
    v += ds.size(a) * ds.size(b) # 便利になる度合(差分)をどんどん足していく
  end
  ds.unite(a, b)
end

# 「便利さ」を「不便さ」に反転したのち順番も反転する
max = N * (N - 1) / 2                                # => 6
av                                                   # => [0, 1, 2, 6, 6]
av = av.collect { |e| max - e }                      # => [6, 5, 4, 0, 0]
av = av.reverse                                      # => [0, 0, 4, 5, 6]
puts av

参考

Discussion