Day 013-Q4 — 近似アルゴリズム・ランダム化アルゴリズム

2026-04-26 赤色 / Phase 8 ★★★★★★★★ 最小頂点被覆 2-近似

問題

N 頂点 M 辺の無向グラフで最小頂点被覆。NP 困難なので 2-近似と局所探索を実装、より小さい方を出力。

制約

$2 \le N \le 1000$
$1 \le M \le 5000$
T = 1 秒
連結とは限らない

入出力例

入力例 1

6 7 1
1 2
1 3
2 4
3 4
3 5
4 6
5 6

出力例 1

3

ヒント (段階的開示)

ヒント1: 方向性
2-近似: 最大マッチングの両端点。局所探索: ランダム除去 + 補修で改善。
ヒント2: アプローチ
局所探索: 頂点を1つ除去 → 未被覆辺の他端点を追加 → 改善判定。
ヒント3: 誘導
time.time() で時間管理、time_limit*0.9 で探索終了。

模範解答 (Python)

import sys
import random
import time

input = sys.stdin.readline

def solve():
    N, M, T = map(int, input().split())
    edges = []
    adj = [[] for _ in range(N + 1)]
    for _ in range(M):
        u, v = map(int, input().split())
        edges.append((u, v))
        adj[u].append(v); adj[v].append(u)

    def is_cover(cover):
        return all(u in cover or v in cover for u, v in edges)

    def two_approx():
        matched = set()
        cover = set()
        for u, v in edges:
            if u not in matched and v not in matched:
                matched.add(u); matched.add(v)
                cover.add(u); cover.add(v)
        return cover

    def local_search(init_cover, time_limit):
        cover = set(init_cover)
        best = set(cover)
        start = time.time()
        while time.time() - start < time_limit:
            if not cover:
                break
            v = random.choice(list(cover))
            cover.remove(v)
            uncovered = [(u, w) for u, w in edges
                        if (u == v or w == v) and u not in cover and w not in cover]
            if uncovered:
                for u, w in uncovered:
                    other = w if u == v else u
                    cover.add(other)
                if len(cover) >= len(best):
                    cover.add(v)
                    for u, w in uncovered:
                        other = w if u == v else u
                        if other != v:
                            cover.discard(other)
            if len(cover) < len(best) and is_cover(cover):
                best = set(cover)
        return best

    approx2 = two_approx()
    local = local_search(approx2, T * 0.9)
    print(min(len(approx2), len(local)))

solve()

Step-by-Step 解説

12-近似
最大マッチング M* の両端点。$|C| = 2|M*| \le 2|OPT|$。
2局所探索
頂点除去 → 未被覆辺の他端点追加 → 改善判定。
3時間管理
time.time() で経過監視、時間制限の 90% で探索終了。
4近似比
頂点被覆は UGC のもとで 1.5 未満不可能と予想。

よくあるミス

ミス原因正しい書き方
is_cover 確認なし被覆崩れの可能性最後に assert
time.time() 精度OS依存time.perf_counter()
辺走査順依存2-近似が変動ランダムシャッフルで安定化

次のステップ

  • 最大独立集合(最小頂点被覆の補集合)

自己評価

自分の回答

気づき・メモ