Подмаски и надмаски
Идиома перебора подмасок, почему всего их 3^n, и как перебирать надмаски.
2 мин
Подмаска маски — это число, все единичные биты которого есть и у . На языке множеств — подмножество.
Перебираются подмаски одной идиомой, и она достаточно неочевидна, чтобы её запомнить:
for (int s = m; ; s = (s - 1) & m) {
... // здесь s — очередная подмаска m
if (s == 0) break;
}
Почему это работает
Пусть — подмаска . Вычитание единицы «занимает» из младшей единицы : она превращается в ноль, а нули правее — в единицы. Среди этих единиц могут оказаться биты, которых нет в ; операция & m их отбрасывает.
Получается следующая по убыванию подмаска. Значит, перебор идёт по всем подмаскам в порядке убывания и заканчивается на нуле.
Проверка на ноль стоит после тела цикла. Если поставить её в условие, пустая подмаска не обработается — и это самая частая ошибка в этой идиоме.
Почему всего
Если перебрать подмаски каждой маски, суммарное количество итераций равно . Доказательство в одну строку: каждая пара «маска, подмаска» задаётся тем, в каком из трёх состояний находится каждый бит — есть и там и там, есть только в маске, нет нигде.
Проверено перебором: при пар ровно 27, при — 59 049, при — 14 348 907, то есть в точности .
При это — уже много. Замер: перебор подмасок всех масок при занял 2674 мс, а при — 265 мс. Если задача требует считать что-то по подмаскам для всех масок, обычно нужен другой способ — суммы по подмаскам за .
Надмаски
Симметрично: надмаска — это число, у которого есть все биты и, возможно, ещё какие-то. Перебираются они через дополнение:
int full = (1 << n) - 1;
for (int s = full ^ m; ; s = (s - 1) & (full ^ m)) {
int superset = s | m; // очередная надмаска
if (s == 0) break;
}
То есть перебираем подмаски дополнения и добавляем к ним саму .
Смежное
- Маска как множество;
- Суммы по подмаскам — когда слишком много.