ebisukeプログラミング初心者脱出黙示録

30歳を過ぎてから始めたプログラミングと競プロの記録。Pythonで取り組んでいます。Arduinoで電子工作も

【AtCoder版!蟻本】ATC001 B - Union Find【Union-Find】

f:id:ebisuke33:20210620224439p:plain

AtCoder版!蟻本のUnion-Find木の例題としてあげられているATC001 B - Union FindをPythonで解いていきます。

B - Union Find

ATC001 B - Union Find

問題文

N 頂点の、単純とは限らない無向グラフを考えます。 初期状態では、頂点のみが存在し、辺は全く存在せず、全ての頂点が孤しているとします。 以下の 2 種類のクエリが、Q 回与えられます。

・連結クエリ: 頂点 A と、頂点 B を繋ぐ辺を追加します。
・判定クエリ: 頂点 A と、頂点 B が、連結であるかどうか判定します。連結であれば Yes、そうでなければ No を出力します。
クエリを順番に処理し、判定クエリへの回答を出力して下さい。 この際、同じ辺が何度も追加されることや、自分自身への辺が追加されることもある事に注意してください。

連結であるとは、頂点 A から頂点 B まで辺をたどって到達可能であることを意味します。
A と B が同じ頂点の場合、連結であると定義します。 グラフは無向であるため、連結クエリによって頂点 A,B 間の辺が追加されると、A から B へも B から A へも辿れるようになります。

制約

・1 行目には、頂点の個数を表す整数 N(1≦N≦100,000) と、クエリの個数を表す整数 Q(1≦Q≦200,000) が、スペース区切りで与えられる。
・2 行目からの Q 行のうち i 行目には、i 番目のクエリの種類を表す整数 Pi(0≦Pi≦1) と、クエリの対象となる頂点の番号を表す整数 Ai,Bi(0≦Ai,Bi≦N−1) が、 スペース区切りで与えられる。
・Pi=0 の時、そのクエリが連結クエリであることを表す。
・Pi=1 の時、そのクエリが判定クエリであることを表す

解答例

max_num = int(1e7)
# ノードiの親がpar[i]
par = [0 for _ in range(max_num)]
# 木の深さ
rank = [0 for _ in range(max_num)]

# 初期化
def init(n):
    for i in range(n):
        # 初期化では自身を親とする
        par[i] = i
        # rankは0
        rank[i]= 0

# 木の根を求める
def find(x):
    # 自身が親なら自身の番号を返す
    if par[x] == x:
        return x
    else:
        # 根を張り替える
        par[x] = find(par[x])
        return par[x]

# xとyの属する集合を併合
def unite(x,y):
    root_x = find(x)
    root_y = find(y)
    # 同じグループの場合
    if root_x == root_y:
        return
    # ランクの大きいほうの根に小さいほうの根をつなぐ
    if rank[root_x] < rank[root_y]:
        par[root_x] = root_y
    else:
        par[root_y] = root_x
        # ランクが同じ場合は1だけ増加
        if rank[x] == rank[y]:
            rank[x] += 1

# xとyが同じ集団か
def same(x,y):
    return find(x) == find(y)


n, q = map(int,input().split())

init(n)

for i in range(q):
    p, a, b = map(int, input().split())
    # pが0ならaとbを併合
    if p == 0:
        unite(a, b)

    # pが1なら判定
    else:
        if same(a, b):
            print("Yes")
        else:
            print("No")

解説

ATC001 Bのページに解説があり、そちらを参考にしたほうが良いとは思いますが、一応記事を書いていきます。

Union-Findの練習問題で、pが0ならaとbを連結し、pが1ならaとbが連結かどうかを答える問題です。

pが0のとき、aとbをunite関数で連結させます。
このとき、根の深さを比べて、根が深いほうに浅い集合を連結させることで必要以上に根が深くなることを防ぎます。

pが1のときは、aとbの根をfind関数で調べます。
根が同じであれば、同じ集団に属しており連結であることがわかります。

この判定結果に従って解答することでOKです。



AtCoder版!蟻本(初級編)の記事リンクページはこちら
ebisuke33.hatenablog.com