Day 051-Q5 — 確率的IS期待値DP(Probabilistic Independent Set + Bit DP)

2026-06-04 赤色 Master / Phase 8+ ★★★★★★★★★ 確率DP / Bit DP / 独立集合 / mod 逆元

問題

$N$ 頂点 $M$ 辺のグラフ $G$ が与えられる($N \le 20$)。各頂点 $v$ は確率 $p_v$ で独立にアクティブになる。

アクティブな頂点集合 $S$ が独立集合($S$ 内に辺がない)である確率を $\bmod 998244353$ で求めよ。

さらに $Q$ 個のクエリ $v$ に対し、「頂点 $v$ がアクティブかつ $S$ が独立集合である」確率も求めよ。

制約

パラメータ範囲
$N$$1 \le N \le 20$
$M$$0 \le M \le N(N-1)/2$
$Q$$1 \le Q \le N$
$p_v$分数で与えられる(分母 $\le 10^9$)
時間制限3秒

入出力例

入力例 1

3 2 1
1 2
1 2
1 2
1 2
2 3
1

出力例 1

499122177
499122178

$p_1=p_2=p_3=1/2$、辺は 1-2, 2-3。IS の確率 = $3/8 \bmod p$、頂点1がアクティブかつIS = $1/4 \bmod p$。

概念図: ビット DP による独立集合の列挙

グラフ (N=3, 辺: 1-2, 2-3) 1 2 3 全部分集合 S ∈ {000, 001, ..., 111} の独立集合判定 S (bits) 頂点集合 IS? P(S) = Π p_v・Π q_v 000 YESq₁q₂q₃ = 1/8 001{1} YESp₁q₂q₃ = 1/8 010{2} YESq₁p₂q₃ = 1/8 011{1,2} NO (辺1-2) 100{3} YESq₁q₂p₃ = 1/8 101{1,3} YESp₁q₂p₃ = 1/8 110{2,3} NO (辺2-3) 独立集合の総確率 P(IS) = 1/8+1/8+1/8+1/8+1/8 = 5/8 (∅,{1},{2},{3},{1,3}) 条件付き確率 (v=1) P(v1 active AND IS) = P({1})+P({1,3}) = 1/8+1/8 = 2/8 = 1/4

ヒント(段階的開示)

ヒント1: 方向性
$N \le 20$ なので $2^N \le 10^6$ の全部分集合を列挙できる。各部分集合 $S$ が独立集合かどうかをビット DP で前処理し、各独立集合の確率を足し合わせる。
ヒント2: アプローチ
  • 独立集合判定(前処理): is_indep[0] = True、$S$ の最下位ビット $v$ について is_indep[S] = is_indep[S^(1<<v)] and not (adj_mask[v] & (S^(1<<v)))
  • 確率計算: $P(S) = \prod_{v \in S} p_v \cdot \prod_{v \notin S} (1-p_v)$ を $\bmod p$ で計算
  • 答え: 全独立集合の $P(S)$ の和
  • 条件付き: $v$ がビット $v$ を持つ独立集合のみ足し込む
ヒント3: 実装骨格
size = 1 << N
is_indep = [False]*size; is_indep[0] = True

for S in range(1, size):
    v = (S & -S).bit_length() - 1  # 最下位ビット位置
    rest = S ^ (1 << v)
    is_indep[S] = is_indep[rest] and not (adj_mask[v] & rest)

total = 0
conditional = [0]*N
for S in range(size):
    if not is_indep[S]: continue
    pr = 1
    for v in range(N):
        pr = pr * (p[v] if S&(1<

模範解答 (Python)

import sys
input = sys.stdin.readline
MOD = 998244353

def modinv(a, m=MOD): return pow(a, m-2, m)

def solve():
    N, M, Q = map(int, input().split())
    p = []; q = []
    for _ in range(N):
        num, den = map(int, input().split())
        pv = num * modinv(den) % MOD
        p.append(pv); q.append((1-pv)%MOD)

    adj_mask = [0]*N
    for _ in range(M):
        u, v = map(int, input().split())
        u -= 1; v -= 1
        adj_mask[u] |= (1<

Step-by-Step 解説

1独立集合の高速列挙(ビット DP)
$\text{is\_indep}[S]$:$S$ の最下位ビット $v$ を除いた部分集合 $S' = S \setminus \{v\}$ が独立集合、かつ $v$ が $S'$ 内の頂点と非隣接、の両方が成立するとき TRUE。$O(2^N)$。
2各独立集合の確率(mod 逆元)
$P(S) = \prod_{v \in S} p_v \cdot \prod_{v \notin S} (1-p_v)$。分数は mod 逆元で整数に変換。
3総和の計算
全独立集合 $S$ について $P(S)$ を足し合わせる。条件付き確率は $v \in S$ のときのみ加算。
4最下位ビット位置の計算
(S & -S) で最下位ビットを取り出し、.bit_length() - 1 でゼロ始まりのビット位置を得る。

計算量

独立集合前処理: $O(2^N)$
確率計算: $O(2^N \cdot N)$
全体: $O(2^N \cdot N)$($N=20$ で $\approx 2 \times 10^7$、十分高速)
空間: $O(2^N + N)$

よくあるミス

ミス原因正しい書き方
最下位ビット位置計算の誤りbin(S&-S).count('0') では誤り(S & -S).bit_length() - 1
確率の積で浮動小数点を使う精度誤差で答えが変わるmod 逆元で整数計算
$q_v = 1-p_v$ の計算で負になるPython の mod 演算は負を返さないが念のため(1-pv) % MOD
辺の入力で 1-indexed を 0-indexed に変換しないadj_mask の参照がズレるu -= 1; v -= 1

次のステップ

  • 発展問題: 独立集合の個数を mod p で数え上げ(包除原理 + 頂点被覆数)
  • 関連: Profile DP(行ごとのビット集合 DP、グリッドグラフ)
  • 応用: 最大独立集合数 $\alpha(G)$ の計算(Bron-Kerbosch や分割統治)

自己評価