Day 029-Q4 — 乱択最小カット(Karger's Algorithm)

2026-05-12 赤色 Master / Phase 8+ ★★★★★★★★★ 乱択・グラフ縮約

問題

$N$ 頂点 $M$ 辺の無向グラフのグローバル最小カット(最小辺カット数)を求めよ。

制約

$2 \le N \le 500$
$N-1 \le M \le N(N-1)/2$
連結・自己ループなし・多重辺なし

入出力例

入力例 1

4 5
1 2
1 3
2 3
2 4
3 4

出力例 1

2

ヒント (段階的開示)

ヒント1: 方向性
辺をランダム選択→縮約。頂点数 2 で残った辺数 = カット数候補。成功確率 $\Omega(1/N^2)$。
ヒント2: アプローチ
Karger-Stein なら $\Omega(1/\log N)$ で $O(N^2 \log^3 N)$。シンプル版でも $N=500$ なら $O(N^4 \log N)$ で間に合う。
ヒント3: 実装
隣接行列 + 多重辺カウントで管理。試行回数を増やして信頼度を上げる。

模範解答 (Python)

import sys
import random
import math
input = sys.stdin.readline

def karger_once(adj, n):
    active = list(range(n))
    while len(active) > 2:
        edges = []
        for i in range(len(active)):
            for j in range(i+1, len(active)):
                u, v = active[i], active[j]
                if adj[u][v] > 0:
                    for _ in range(adj[u][v]):
                        edges.append((u, v))
        if not edges:
            break
        eu, ev = random.choice(edges)
        for w in active:
            if w != eu and w != ev:
                adj[eu][w] += adj[ev][w]
                adj[w][eu] = adj[eu][w]
                adj[ev][w] = 0
                adj[w][ev] = 0
        adj[eu][ev] = 0
        adj[ev][eu] = 0
        active.remove(ev)
    u, v = active[0], active[1]
    return adj[u][v]

def solve():
    N, M = map(int, input().split())
    original = [[0]*N for _ in range(N)]
    for _ in range(M):
        u, v = map(int, input().split())
        u -= 1; v -= 1
        original[u][v] += 1
        original[v][u] += 1
    trials = max(50, int(math.ceil(N * N * math.log2(N + 1))))
    trials = min(trials, 300)
    best = M
    for _ in range(trials):
        adj = [row[:] for row in original]
        result = karger_once(adj, N)
        if result < best:
            best = result
    print(best)

solve()

Step-by-Step 解説

1Karger の縮約
辺をランダムに選び両端をマージ、自己ループ削除。頂点 2 で残った辺数 = カット候補。
2成功確率分析
$N$ 回の縮約で最小カット辺を1本も選ばない確率 $\ge \binom{N}{2}^{-1}$。
3繰り返し
$T = \binom{N}{2} \ln N$ で失敗確率 $1/N$ 以下。
4実装注意
多重辺を展開した辺リストからランダム選択は遅いので、重み付きサンプリング推奨。

よくあるミス

ミス原因正しい書き方
active 削除漏れ縮約後も残るactive.remove(ev) 必須
自己ループ残りマージ時に消さないadj[eu][ev] = adj[ev][eu] = 0
試行回数不足確率的失敗$N^2 \log N$ 程度
adj の参照ミス同一オブジェクト使用[row[:] for row in original]

次のステップ

  • Karger-Stein で $O(N^2 \log^3 N)$

自己評価

自分の回答

気づき・メモ