Day 054-Q5 — LP緩和 + 乱択丸め近似(MAX-SAT)

2026-06-07 赤色 Master / Phase 8+ ★★★★★★★★★ LP緩和 / 乱択近似 / MAX-SAT / 条件付き期待値法

問題

$N$ 変数の MAX-SAT 問題を近似的に解く。$M$ 個の節(clause)が与えられ、各節はリテラルの論理和(OR)である。各節には重み $w_j > 0$ がある。

満たされる節の重みの総和 を最大化する変数割り当て $x_1, \ldots, x_N \in \{0, 1\}$ を求めよ。

以下の近似アルゴリズムを実装し最大スコアを出力せよ:

  1. 乱択丸め(均等割り当て $\hat{x}_i = 0.5$ から各変数を確率 $0.5$ で 0/1 に決定)を $T$ 回試行
  2. $N \le 20$ の場合は全探索で厳密最適解

制約

パラメータ範囲
$N$$1 \le N \le 20$
$M$$1 \le M \le 100$
$T$$1 \le T \le 1000$
$w_j$$1 \le w_j \le 100$
$k_j$$1 \le k_j \le N$(節のリテラル数)

入出力例

入力例 1

3 3 100
2 2 1 -2
3 2 -1 3
1 3 1 2 3

出力例 1

6

節1: (x1 ∨ ¬x2) 重み2, 節2: (¬x1 ∨ x3) 重み3, 節3: (x1 ∨ x2 ∨ x3) 重み1。x1=1,x2=0,x3=1 で全節満足、スコア=6。

概念図: 乱択丸め近似の理論保証

乱択丸め: 節のリテラル数 k と満足確率の関係 50% k=1 75% k=2 87.5% k=3 93.75% k=4 →100% k→∞ 0% 50% 100% 節の満足確率 1 - (1/2)^k = 1 - 2^{-k} k≥3 で 87.5%+ T=1000回の乱択丸め: 期待値以上のスコアが高確率で得られる(失敗確率 ≤ (1-p)^T ≈ 0) 条件付き期待値法: 確率的に最良解と同等以上を決定論的に保証

ヒント(段階的開示)

ヒント1: 方向性
LP 緩和解として「全変数 $\hat{x}_i = 0.5$」を使う。乱択丸め = 各変数を確率 $0.5$ で $1$ に設定するランダム割り当てと等価。節のリテラル数 $k$ に対し満足確率 = $1 - 2^{-k} \ge 0.5$。$T$ 回試行で高品質解が得られる。
ヒント2: アプローチ
  • $N \le 20$ なので全探索 $O(2^N \cdot M)$ も可能
  • 乱択丸め $T$ 回で失敗確率は指数減少($T=1000$ で十分)
  • 条件付き期待値法: 変数を 1 つずつ決め、残り変数の期待スコアが大きい方を選ぶ
ヒント3: 評価関数
def evaluate(assignment, clauses):
    """assignment: list[bool] 1-indexed"""
    score = 0
    for w, literals in clauses:
        for l in literals:
            val = assignment[abs(l)]
            satisfied = val if l > 0 else not val
            if satisfied:
                score += w
                break  # 節が満たされたら次へ
    return score

# ランダム割り当て T 回
import random
best = 0
for _ in range(T):
    asgn = [False] + [random.random() < 0.5 for _ in range(N)]
    best = max(best, evaluate(asgn, clauses))

模範解答 (Python)

import sys
import random
input = sys.stdin.readline

def solve():
    N, M, T = map(int, input().split())
    clauses = []
    for _ in range(M):
        line = list(map(int, input().split()))
        w, k = line[0], line[1]
        clauses.append((w, line[2:2+k]))

    def evaluate(assignment):
        score = 0
        for w, lits in clauses:
            for l in lits:
                val = assignment[abs(l)]
                if (val if l > 0 else not val):
                    score += w; break
        return score

    # 乱択丸め T 回
    best = 0
    for _ in range(T):
        asgn = [False] + [random.random() < 0.5 for _ in range(N)]
        s = evaluate(asgn)
        if s > best: best = s

    # 全探索(N<=20)
    for mask in range(1 << N):
        asgn = [False] + [bool((mask >> i) & 1) for i in range(N)]
        s = evaluate(asgn)
        if s > best: best = s

    print(best)

solve()

Step-by-Step 解説

1MAX-SAT の LP 緩和
MAX-SAT は NP 困難。LP 緩和では $x_i \in [0, 1]$ を許す。均等割り当て $\hat{x}_i = 0.5$ は最もシンプルな実行可能 LP 解。この LP 解から乱択丸めを行う。
2乱択丸めの理論保証
節のリテラル数 $k$ に対し、節が満たされる確率 = $1 - (1/2)^k$。$k=1$ で $1/2$(最悪)、$k \ge 3$ で $7/8$ 以上(MAX-3SAT の $7/8$ 近似保証の根拠)。
3条件付き期待値法(決定論的)
各変数を順に決める際、「残り変数をランダムにしたときの期待スコア」が高い方を選択。これにより乱択アルゴリズムの期待値以上のスコアを決定論的に出力できる。$O(2^{N-i} M)$ で $i$ 番目を決める。
4全探索による厳密解(N≤20)
$2^{20} = 1048576$ 通り × $M=100$ 節 = $10^8$ 演算。Python では TLE の場合があるが、理論上は正確。$N$ が大きい場合は meet-in-the-middle($N=40$ まで)を検討。

計算量

乱択丸め: $O(T \cdot M \cdot \max k)$ — $T=1000$, $M=100$ で $O(10^5)$
全探索: $O(2^N \cdot M)$ — $N=20, M=100$ で $O(10^8)$(注意: Python は遅い)
条件付き期待値法: $O(2^N \cdot M)$ — 全探索と同等

よくあるミス

ミス原因正しい書き方
否定リテラルの符号ミスl < 0 を見逃すval if l > 0 else not val
節が満たされた後もチェック継続無駄な計算break で早期終了
assignment の 0 番インデックス1-indexed 変数番号を 0-indexed で使うassignment[abs(l)](1-indexed)
T=1 で試行乱択は失敗しうるT=1000 以上で高確率保証

次のステップ

  • 発展問題: MAX-3SAT の $7/8$ 近似を厳密に実装(Johnson's Algorithm)
  • 関連: MAX-CUT の Goemans-Williamson $0.878$ 近似(SDP 緩和 + 乱択丸め)
  • 応用: Weighted MAX-SAT の PTAS(多項式時間近似スキーム)

自己評価