問題
$N$頂点$M$辺の無向グラフ(多重辺・自己ループなし)と整数$K$が与えられる。辺は入力順に$1$〜$M$の番号を持つ。
以下の貪欲な森分解アルゴリズムに従って、各辺が証明書に含まれるかを判定せよ。
アルゴリズムの定義
$K$本の森$F_1,\dots,F_K$(Union-Findで管理)を空の状態から用意する。辺を入力順に処理し、辺$(u,v)$に対して$i=1,\dots,K$の順に「$F_i$上で$u,v$がまだ非連結か」を調べ、最小の$i$でサイクルができないなら$F_i$に加える。どの$F_i$でもサイクルができるなら証明書から除外する。
各辺について、証明書に含まれるなら1、除外されるなら0を出力せよ。
入力形式
N M K
u_1 v_1
:
u_M v_M
制約
$2 \le N \le 2\times10^5$
$1 \le M \le 2\times10^5$
$1 \le K \le 20$
$1 \le u_i,v_i \le N$、$u_i\ne v_i$
入出力例
入力例1 (K=1)
4 6 1
1 2
2 3
3 4
4 1
1 3
2 4出力例1
1
1
1
0
0
0入力例2 (K=2)
4 6 2
1 2
2 3
3 4
4 1
1 3
2 4出力例2
1
1
1
1
1
1概念図
ヒント(段階的開示)
ヒント1(方向性)
「グラフがサイクルを作らないように辺を選ぶ」操作は、グラフィックマトロイド上の独立集合の拡張である。1本のUnion-Findでこれを行えば最大全域森が求まるが、これを$K$回、前の森で使わなかった辺だけを対象に繰り返すことで$K$個のマトロイドの合併(matroid union)が実現できる。
ヒント2(アプローチ)
Nash-Williams/Tutteの定理により、この貪欲な$K$本森分解で除外されなかった辺集合(証明書)だけを使っても、元のグラフの最小カットが$K$未満かどうかを正しく判定できる。証明書のサイズは高々$K(N-1)$本になり、$M$が大きくても大幅に間引ける。
ヒント3(誘導)
def build_certificate(n, edges, k):
parent = [list(range(n + 1)) for _ in range(k)]
def find(f, x):
while parent[f][x] != x:
parent[f][x] = parent[f][parent[f][x]]
x = parent[f][x]
return x
def union(f, x, y):
rx, ry = find(f, x), find(f, y)
if rx == ry:
return False
parent[f][rx] = ry
return True
kept = []
for (u, v) in edges:
placed = False
for f in range(k):
if union(f, u, v):
placed = True
break
kept.append(1 if placed else 0)
return kept
模範解答 (Python)
import sys
def main():
data = sys.stdin.read().split()
idx = 0
n, m, k = int(data[idx]), int(data[idx+1]), int(data[idx+2]); idx += 3
parent = [list(range(n + 1)) for _ in range(k)]
def find(f, x):
root = x
while parent[f][root] != root:
root = parent[f][root]
while parent[f][x] != root:
parent[f][x], x = root, parent[f][x]
return root
def union(f, x, y):
rx, ry = find(f, x), find(f, y)
if rx == ry:
return False
parent[f][rx] = ry
return True
out = []
for _ in range(m):
u, v = int(data[idx]), int(data[idx+1]); idx += 2
placed = False
for f in range(k):
if union(f, u, v):
placed = True
break
out.append("1" if placed else "0")
print("\n".join(out))
main()
Step-by-Step 解説
11本の森分解 = 最大全域森
K=1の場合はUnion-Findで辺を順に見てサイクルを作る辺を除外する、Kruskal法のUnion-Find部分と同じ操作になる。
K=1の場合はUnion-Findで辺を順に見てサイクルを作る辺を除外する、Kruskal法のUnion-Find部分と同じ操作になる。
2複数の森への割り当て
K≥2のとき、F1で弾かれた辺はF2に「もう一度挑戦」する機会が与えられる。辺は最初にサイクルを作らずに収まる森に割り当てられる。
K≥2のとき、F1で弾かれた辺はF2に「もう一度挑戦」する機会が与えられる。辺は最初にサイクルを作らずに収まる森に割り当てられる。
3証明書になる理由
Nash-Williams/Tutteのマトロイド合併定理により、この貪欲法で除外されなかった辺だけを残しても元のグラフがK-辺連結かどうかの判定結果は変わらないことが保証される。
Nash-Williams/Tutteのマトロイド合併定理により、この貪欲法で除外されなかった辺だけを残しても元のグラフがK-辺連結かどうかの判定結果は変わらないことが保証される。
4証明書のサイズ
K本の森はそれぞれ高々N-1本の辺しか持てないため、証明書に残る辺の総数は高々K(N-1)本。密なグラフでも大幅に間引ける。
K本の森はそれぞれ高々N-1本の辺しか持てないため、証明書に残る辺の総数は高々K(N-1)本。密なグラフでも大幅に間引ける。
5計算量
各辺につき最大K回のfind/unionを行うので全体でO(MKα(N))。K≤20程度ならM=2×10^5でも十分高速。
各辺につき最大K回のfind/unionを行うので全体でO(MKα(N))。K≤20程度ならM=2×10^5でも十分高速。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 辺を1本のUnion-Findだけで判定し最初の森で弾かれた辺をそのまま除外 | K=1の最大全域森の考え方から抜け出せていない | K本の独立したUnion-Findを用意し順に試す |
| K本のUnion-Findを1つの配列で共有してしまう | parentを森ごとに分けずに使い回している | 森ごとに独立したparent配列を用意する |
| 辺の割り当てを最後の森から順に試してしまう | 貪欲法の走査順序を逆にしている | 必ず小さい番号の森から順に試す |
| 経路圧縮を実装せずfindがO(N)になり全体がO(MKN)に悪化 | Union-Findの高速化を省略している | findに経路圧縮を実装する |
| 証明書に残った辺だけで最短路や連結成分数まで保存されると誤解する | 証明書が保証するのはK-辺連結性の判定に限られることを理解していない | 証明書は最小カットがK未満かどうかの判定用と理解する |
次のステップ
- 発展: 得られた証明書に対してStoer-Wagner法(Day095/Day115)を適用し、密なグラフでの大域最小カット計算を高速化せよ。証明書を使わない場合との実行時間比較を行うこと。
- 発展2: 辺をランダムな順序でシャッフルしてから処理した場合に、証明書に残る辺の総数や構成がどう変化するかを実験的に観察せよ。