← Все вопросы

SOS DP (sum over subsets): как за O(n·2^n) посчитать сумму по всем подмаскам каждой маски?

Задан 10 месяцев назад1.1к просмотров2 ответа
10

Дан массив a[mask] для всех масок 0mask<2n0 \le mask < 2^n. Нужно для каждой маски посчитать F[mask]=submaska[sub]F[mask] = \sum_{sub \subseteq mask} a[sub] — сумму по всем её подмаскам. Наивно перебор подмасок даёт O(3n)O(3^n). Слышал про SOS DP за O(n2n)O(n \cdot 2^n) — как это работает?

2 ответа

15
✓ Принятый ответ — помог автору

SOS DP (Sum over Subsets) — это, по сути, nn-мерное префиксное суммирование по гиперкубу из 0/1 по каждому биту. Состояние dp[i][mask] = сумма по тем подмаскам mask, которые отличаются от mask только в первых i битах. После обработки всех n битов получаем сумму по всем подмаскам.

Компактная in-place версия:

int n;
vector<long long> f(1 << n);   // изначально f = a
// f[mask] = a[mask];
for (int i = 0; i < n; ++i)
  for (int mask = 0; mask < (1 << n); ++mask)
    if (mask & (1 << i))
      f[mask] += f[mask ^ (1 << i)]; // добавляем подмаску без бита i
// теперь f[mask] = sum_{sub ⊆ mask} a[sub]

Идея цикла по i: фиксируем бит i. Если он установлен в mask, к текущему f[mask] добавляем f маски без этого бита — так на каждом шаге «развязывается» один бит. Время O(n2n)O(n \cdot 2^n), память O(2n)O(2^n). Для n=20n=20 это 20106=210720 \cdot 10^6 = 2\cdot10^7 — быстро.

6

Двойственный вариант — сумма по надмаскам (supersets): просто меняем условие на if (!(mask & (1<<i))) и f[mask] += f[mask | (1<<i)]. А обратное преобразование (Мёбиус по подмножествам, чтобы из сумм восстановить исходный a) — тот же цикл, но с -=:

for (int i = 0; i < n; ++i)
  for (int mask = 0; mask < (1 << n); ++mask)
    if (mask & (1 << i)) f[mask] -= f[mask ^ (1 << i)];

Это прямой и обратный «зета/мёбиус»-трансформ — фундамент для subset-sum свёрток (OR-свёртка). Порядок циклов важен: внешний по битам, внутренний по маскам, не наоборот.

Ваш ответ

Войдите, чтобы ответить на вопрос.