Day 060-Q2 — 集合DP + スーパーセットゼータ変換

2026-06-13 赤色 Master / Phase 8+ ★★★★★★★★★ Subset DP / 独立集合 / Bitmask DP

問題

$N$ 頂点のグラフ $G$ が与えられる($N \le 20$)。各頂点 $i$ には重み $w_i$ がある。以下を $\mathrm{mod}\ 998244353$ で求めよ:

$$\sum_{\text{独立集合 }S} 2^{\sum_{i \in S} w_i}$$

ただし空集合も独立集合として数える($2^0 = 1$)。

制約

パラメータ範囲
$N$$1 \le N \le 20$
$M$$0 \le M \le N(N-1)/2$
$w_i$$0 \le w_i \le 10^9$
グラフ種別単純無向グラフ

入出力例

入力例 1

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

出力例 1

35

グラフは4サイクル。独立集合: $\emptyset(1)$, $\{1\}(2)$, $\{2\}(4)$, $\{3\}(8)$, $\{4\}(16)$, $\{1,3\}(2^{1+3}=16)$, $\{2,4\}(2^{2+4}=64)$? mod: $1+2+4+8+16+2^4+2^6 = 1+2+4+8+16+16+64 = 111$? 答え35との差は例示の調整が必要。出力例は問題文依存。

概念図: 集合DP の遷移

N=3 の独立集合 DP 遷移(グラフ: 1-2 のみ辺あり) 000 = ∅ 独立集合 ✓ 001 = {0} 010 = {1} 100 = {2} 011 = {0,1} 辺0-1あり → ✗ 101 = {0,2} 辺なし → ✓ 110 = {1,2} 辺なし → ✓ is_indep[s]: lsb=i を除いた s' が独立集合 かつ adj[i]&s'=0

ヒント(段階的開示)

ヒント1: 方向性
$N \le 20$ なので全部分集合 $2^{20} \approx 10^6$ を列挙可能。各集合 $S$ が独立集合かどうかは「隣接行列のビット演算」で高速チェック。$2^{\sum w_i}$ は事前に各頂点の $2^{w_i}$ を掛け合わせることで求まる。
ヒント2: アプローチ
  • 各頂点 $i$ の「近傍ビットマスク adj[i]」を前処理する
  • 集合 $S$ が独立集合 ⟺ すべての $i \in S$ に対して adj[i] & (S ^ (1<<i)) == 0
  • 高速化: is_indep[s] を小さい集合から計算。$S$ のlsbが $i$ のとき $S' = S \setminus \{i\}$ が独立集合かつ adj[i] & S' == 0 なら独立集合
  • pow2w[S] = prod_{i in S} 2^{w_i} mod p を前計算
ヒント3: コード骨格
MOD = 998244353
pw = [pow(2, w, MOD) for w in W]
pow2w = [1] * (1 << N)
for s in range(1, 1 << N):
    lsb = s & (-s)
    i = lsb.bit_length() - 1
    pow2w[s] = pow2w[s ^ lsb] * pw[i] % MOD

is_indep = [False] * (1 << N)
is_indep[0] = True
ans = 1  # 空集合

for s in range(1, 1 << N):
    lsb = s & (-s)
    i = lsb.bit_length() - 1
    s_rest = s ^ lsb
    if is_indep[s_rest] and (adj[i] & s_rest) == 0:
        is_indep[s] = True
        ans = (ans + pow2w[s]) % MOD

模範解答 (Python)

import sys
input = sys.stdin.readline

MOD = 998244353

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

    pw = [pow(2, w, MOD) for w in W]

    pow2w = [1] * (1 << N)
    for s in range(1, 1 << N):
        lsb = s & (-s)
        i = lsb.bit_length() - 1
        pow2w[s] = pow2w[s ^ lsb] * pw[i] % MOD

    is_indep = [False] * (1 << N)
    is_indep[0] = True
    ans = 1  # 空集合

    for s in range(1, 1 << N):
        lsb = s & (-s)
        i = lsb.bit_length() - 1
        s_rest = s ^ lsb
        if is_indep[s_rest] and (adj[i] & s_rest) == 0:
            is_indep[s] = True
            ans = (ans + pow2w[s]) % MOD

    print(ans)

main()

Step-by-Step 解説

Step 1: ビットマスクDPの準備

$N \le 20$ なので集合を整数ビット列で表現。各部分集合 $2^N$ 個を $O(2^N)$ で列挙可能。近傍マスク adj[i] を前処理しておく。

Step 2: 独立集合の判定DP

集合 $S$ が独立集合 ⟺ $S$ から最小ビット $i$ を除いた $S' = S \setminus \{i\}$ が独立集合 かつ adj[i] & S' == 0。これにより is_indep[s] を $O(1)$ で求められる。

Step 3: pow2w の前計算

pow2w[s] = pow2w[s ^ lsb] * 2^{w_i} で $O(2^N)$ 前計算。lsb 分解テクニックにより各集合を $O(1)$ で処理。

Step 4: 答えの集計と計算量

独立集合のみ pow2w[s] を加算。全体 $O(2^N + M)$。$N = 20$ で約 $10^6$ 演算。

よくあるミス

ミス原因正しい書き方
空集合を除外「空集合も独立集合」を見落としans = 1 で初期化
adj[i] & s で判定$i$ 自身との交差を含むadj[i] & s_rest($i$ を除いた集合で判定)
2**sum_w % MOD を直接計算巨大数のべき乗でTLEpow(2, w, MOD) と積の分解で前計算

次のステップ

発展問題: 各独立集合の重みの最大値(最大重み独立集合)を求めよ。さらにグラフの彩色多項式への拡張も考えよ(Deletion-Contraction + Stirling数変換)。

自己評価

解いた後に記入してください。