Суммы по подмаскам
Префиксные суммы на решётке подмножеств: четыре преобразования за O(2^n · n), обращение Мёбиуса и где их путают.
3 мин
Задача: для каждой маски посчитать
Наивно это — перебор подмасок каждой маски. Есть способ за , и он состоит из трёх строк.
for (int bit = 0; bit < n; bit++)
for (int mask = 0; mask < (1 << n); mask++)
if (mask >> bit & 1) p[mask] += p[mask ^ (1 << bit)];
Почему это верно
Инвариант: после обработки битов величина равна сумме по тем подмаскам , которые отличаются от только этими битами.
В начале () это верно: . Шаг: обрабатывая бит , для масок с этим битом мы прибавляем сумму по маскам, у которых он снят. Учтённые наборы битов объединяются, и инвариант сохраняется.
После всех битов условие «отличаются только этими битами» становится пустым ограничением — учтены все подмаски.
Это в точности префиксные суммы по каждому измерению, только измерений и каждое двоичное. Отсюда и название приёма: сумма по подмножествам, sum over subsets.
Замер: при преобразование занимает 9 мс против 2674 мс у перебора подмасок, при — 2 мс против 265 мс.
Порядок циклов
Внешний цикл — по битам, внутренний — по маскам. Если поменять местами, ответ станет неверным, причём на маленьких тестах часто совпадёт: при он верен всегда, при угадывает примерно в одном случае из десяти, при — примерно в одном из двухсот (проверено на 10 000 случайных наборов для каждого ).
Причина: при внешнем цикле по маскам значение уже содержит вклад младших битов, и слагаемые учитываются повторно.
Обратное преобразование
По восстановить можно тем же кодом с минусом:
for (int bit = 0; bit < n; bit++)
for (int mask = 0; mask < (1 << n); mask++)
if (mask >> bit & 1) f[mask] -= f[mask ^ (1 << bit)];
Развернув все шаги, получим формулу обращения Мёбиуса на решётке подмножеств:
где — число единичных битов. Прямо по ней считать не надо: это снова .
Четыре варианта
| что считаем | условие | знак |
|---|---|---|
| сумма по подмаскам | mask >> bit & 1 |
+= |
| обращение по подмаскам | mask >> bit & 1 |
-= |
| сумма по надмаскам | !(mask >> bit & 1), берём mask | (1 << bit) |
+= |
| обращение по надмаскам | то же | -= |
Выпишите их рядом хотя бы раз: путают их чаще, чем ошибаются в чём-то более сложном.
Где применяется
Считать пары с условием на маски. Например, количество пар с : посчитать, сколько элементов является подмаской каждой маски, и для каждого взять значение на дополнении.
Свёртка по подмножествам. считается за через суммы по подмаскам с разбиением по числу битов.
Включение-исключение по признакам. Когда «хотя бы один признак» считается через «ровно эти признаки», обратное преобразование даёт переход между ними.
Смежное
- Подмаски;
- Префиксные суммы — тот же приём в одном измерении;
- Рюкзак и суммы.