各連結成分が何らかの値を持っているときのUnion Findの書き方

概要

最近F - Construct HighwayA - Reachable Townsが立て続けに解けなくて悲しかったので、お互いに共通する「UnionFindの各連結成分が変数を持っていて、mergeするときにその変数を使ってなんかする」みたいな問題をすっとかけるようにしたいなと思い、考え方を記すことにした。 他にも使う問題があったら足していきたい/教えてほしい。

考え方

各連結成分が持つ変数のことを xで表すことにする。両方の問題で言えることだが、

  1.  xはどのような値なのか?
  2. どの xを持つ連結成分とマージするのか?
  3. どうやってマージするのか?

を考える必要が出てくる。以下それぞれの問題に対して順番に考えていく。

1.  xはどのような値なのか?

ここがうまいこと設定できればこの解き方で解ける可能性が高そう。

F - Construct Highwayについてはその連結成分のうち、次数が足りない頂点群を xとすればよい。(例えば、 x = \{1,1,3,4\}ならその連結成分についてはまだ頂点1は2回、頂点3は1回、頂点4は1回別の連結成分とつなぐ必要があることを表す)

A - Reachable Townsについては、各連結成分が持つ点のy座標の最小値を xとすればよい。

2. どの xを持つ連結成分とマージするのか?

すべての連結成分をなめてその都度 xを見ていると O(N^2)かかり、TLEになってしまう。そこで、各 xがうまく管理されるようにしといてやる必要がある。 xを管理するために、各連結成分について代表点 p xと結びつけておく。

二つの問題に共通していることだが、必要なのは (x,p)というペアが適切にソートされていて、削除や追加、検索にそんなに時間を要さないこと。ということは、setを使えばうまくいきそうだということがわかる。従って、F - Construct Highwayについては、 (x, p)というペアを xの長さの降順にソートされるようにsetに入れておけばよさそうだし、A - Reachable Townsについては (x, p)というペアをそのままsetに入れておけばよさそう(デフォでsetはless<int>()でsortするので)。

そうしたら、あとはsetの先頭と比較していけばよい。F - Construct Highwayなら先頭の二つをmergeしていけばいい。A - Reachable Townsなら xが見ている点のy座標より小さいものについてずっとmergeを繰り返していく。

3. どうやってマージするのか?

ここまでくればもう後は自然に書けそう。ここの処理は当然問題によるが、一応書いておく。

F - Construct Highwayについて、 (x_1, p_1) (x_2, p_2)がmergeされるときは、setから二つをpopしてきて、 x_1 x_2の末尾頂点をpop_backする。そのあと二つを融合させた x_3がsizeが1以上なら (x_3, \mathrm{parent} (p_1))をsetに再挿入。(なんか xのサイズがでかいときにTLEしそうな感じもある?その場合setからコピー&popするんじゃなくてうまいこと参照とか使う必要があるかも、後でmergeしてから二つ目の要素をeraseする方法も試して、時間を比べてみる)

A - Reachable Townsについて、 (x_1, p_1)と現在見ている頂点 (x_2, p_2)(ただし x_2 p_2のy座標)がmergeされるときは、setから (x_1, p_1)をpopしてきて p_1 p_2をmergeする。これを x_1 \lt x_2となる x_1について繰り返したのち、それらの中で最も小さいy座標を xとしたときに (x, \mathrm{parent} (p_2))をsetに再挿入すればよい。

verify

Submission #29501297 - ACL Contest 1