Чем перебор подмасок (for sub = mask; ...) отличается по сложности от SOS DP — почему 3^n?
Видел идиому перебора всех подмасок маски:
for (int sub = mask; sub > 0; sub = (sub - 1) & mask) { ... }
Если так делать для каждой маски от 0 до , какая суммарная сложность? Почему говорят про , а не или ?
2 ответа
Идиома sub = (sub - 1) & mask корректно перебирает все непустые подмаски mask по убыванию. Для одной маски с k установленными битами она делает итераций.
Суммарно по всем маскам: . Считаем комбинаторно — сколько масок имеет ровно k битов? . Тогда:
(по биному Ньютона). Поэтому полный перебор всех подмасок всех масок — это , а не .
Интуиция «»: каждый бит независимо находится в одном из трёх состояний — «не в маске», «в маске и в подмаске», «в маске, но не в подмаске». Три варианта на бит → .
for (int mask = 0; mask < (1 << n); ++mask)
for (int sub = mask; ; sub = (sub - 1) & mask) {
// обработка подмаски sub
if (sub == 0) break; // важно: 0 обрабатываем и выходим
}
Для : — на грани TLE. Если задача сводится к сумме по подмаскам, SOS DP за почти всегда лучше ( против ).
Когда всё-таки нужен и оправдан: задачи вида «разбить множество на части» (set partition DP), где для каждой маски действительно надо перебрать все её разбиения на подмаску и дополнение — там неизбежен, и для () это проходит. А SOS DP помогает только когда нужна именно агрегация (сумма/максимум) по подмаскам, а не индивидуальная обработка каждой пары (подмаска, дополнение).