← Все вопросы

Чем перебор подмасок (for sub = mask; ...) отличается по сложности от SOS DP — почему 3^n?

Задан 24 месяца назад827 просмотров2 ответа
9

Видел идиому перебора всех подмасок маски:

for (int sub = mask; sub > 0; sub = (sub - 1) & mask) { ... }

Если так делать для каждой маски от 0 до 2n12^n - 1, какая суммарная сложность? Почему говорят про 3n3^n, а не 4n4^n или 2n2n2^n \cdot 2^n?

2 ответа

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

Идиома sub = (sub - 1) & mask корректно перебирает все непустые подмаски mask по убыванию. Для одной маски с k установленными битами она делает 2k2^k итераций.

Суммарно по всем маскам: mask2popcount(mask)\sum_{mask} 2^{popcount(mask)}. Считаем комбинаторно — сколько масок имеет ровно k битов? (nk)\binom{n}{k}. Тогда:

k=0n(nk)2k=(1+2)n=3n\sum_{k=0}^{n} \binom{n}{k} \cdot 2^k = (1 + 2)^n = 3^n

(по биному Ньютона). Поэтому полный перебор всех подмасок всех масок — это O(3n)O(3^n), а не 4n4^n.

Интуиция «3n3^n»: каждый бит независимо находится в одном из трёх состояний — «не в маске», «в маске и в подмаске», «в маске, но не в подмаске». Три варианта на бит → 3n3^n.

for (int mask = 0; mask < (1 << n); ++mask)
  for (int sub = mask; ; sub = (sub - 1) & mask) {
    // обработка подмаски sub
    if (sub == 0) break;   // важно: 0 обрабатываем и выходим
  }

Для n=20n=20: 3203.51093^{20} \approx 3.5 \cdot 10^9 — на грани TLE. Если задача сводится к сумме по подмаскам, SOS DP за O(n2n)O(n\cdot 2^n) почти всегда лучше (21072\cdot10^7 против 3.51093.5\cdot10^9).

5

Когда 3n3^n всё-таки нужен и оправдан: задачи вида «разбить множество на части» (set partition DP), где для каждой маски действительно надо перебрать все её разбиения на подмаску и дополнение — там O(3n)O(3^n) неизбежен, и для n18n \le 18 (31841083^{18}\approx 4\cdot10^8) это проходит. А SOS DP помогает только когда нужна именно агрегация (сумма/максимум) по подмаскам, а не индивидуальная обработка каждой пары (подмаска, дополнение).

Ваш ответ

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