Day 020-Q2 — SOS DP(Sum over Subsets DP)

2026-05-03 赤色 Master / Phase 8+ ★★★★★★★★★ Subset Sum DP

問題

長さ $2^N$ の整数配列 $A$, $B$ が与えられる。各マスク $m$ に対して以下を求めよ。

$f(m) = \sum_{s \subseteq m} A[s]$ および $g(m) = \sum_{s : m \subseteq s} B[s]$

制約

$1 \le N \le 20$
$0 \le A_i, B_i \le 10^9$

入出力例

入力例 1

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

出力例 1

f: 1 3 4 10 6 14 15 36
g: 8 4 4 2 4 2 2 1

ヒント (段階的開示)

ヒント1: 方向性
愚直は $O(3^N)$ で TLE。SOS DP は $O(N \cdot 2^N)$ に改善。
ヒント2: アプローチ
$i$ ビット目に注目した DP。dp[i][m] = m のうち、0〜i-1 ビット目だけを「下げる」ことを許した場合の部分集合和。
ヒント3: 誘導
for i in range(N):
    for m in range(1 << N):
        if m >> i & 1:
            f[m] += f[m ^ (1 << i)]

模範解答 (Python)

import sys
input = sys.stdin.readline

def main():
    N = int(input())
    A = list(map(int, input().split()))
    B = list(map(int, input().split()))

    f = A[:]
    for i in range(N):
        for m in range(1 << N):
            if m >> i & 1:
                f[m] += f[m ^ (1 << i)]

    g = B[:]
    for i in range(N):
        for m in range(1 << N):
            if not (m >> i & 1):
                g[m] += g[m | (1 << i)]

    print("f:", *f)
    print("g:", *g)

main()

Step-by-Step 解説

1問題の核心
$f(m) = \sum_{s \subseteq m} A[s]$。愚直は $O(3^N)$、SOS で $O(N \cdot 2^N)$。
2SOS DPのアイデア
「ビット $i$ を下げる」遷移を $i=0,\ldots,N-1$ の順に適用。
3計算量
$N \times 2^N = 20 \times 10^6 = 2 \times 10^7$。
4Superset SOS
$g(m)$ は逆向き: ビット $i$ を「上げる」ことを許す。
5応用例
AND畳み込み、XOR畳み込み、高速ゼータ変換。

よくあるミス

ミス原因正しい書き方
im のループ順を逆にする更新が正しく伝播しない外ループ i、内ループ m
supersetの条件を逆に逆になるnot (m >> i & 1) で下位ビット0なら上へ
$3^N$ 解法と結果比較を怠るバグ気づかず提出N=3 等で手計算と突き合わせる

次のステップ

  • 発展: AND畳み込み $h[k] = \sum_{i \& j = k} A[i] B[j]$ を $O(N \cdot 2^N)$ で
  • OR・XOR畳み込みとの統一的理解

自己評価

自分の回答

気づき・メモ