Бор с глобальными операциями
Двоичный бор с младшим битом сверху: xor всему множеству ленивой маской, прибавление единицы — обменом детей.
3 мин
Глобальные операции над множеством чисел — «поксорить всё с », «прибавить всем единицу» — по отдельности решаются ленивой величиной: обратимая операция переносится на вопрос, а не применяется к данным.
Вместе они так не складываются: нельзя переписать как . Сбрасывать накопленное в массив при каждой смене типа — это на запрос. Здесь и нужен бор.
Устройство
Числа кладутся в двоичный бор, но биты записываются от младшего к старшему: на первом уровне — нулевой бит, на втором — первый, и так далее.
| операция | что делать | стоимость |
|---|---|---|
| xor с | запомнить маску, она меняет чтение битов | |
| прибавить единицу всем | обменять детей корня и уйти в перенос | |
| вычесть единицу у всех | то же, но спуск по заёму | |
| вставить и удалить число | обычный спуск по битам |
Почему прибавление единицы — это обмен
При у всех чисел младший бит меняется на противоположный: это и есть обмен детей корня. У тех чисел, у которых он был единицей, случился перенос — и они все лежат в одном поддереве. Значит достаточно спуститься в него и повторить то же самое на следующем бите.
void increase(int node, int level) {
if (!node || level == BITS) return;
int ones = 1 ^ ((flip >> level) & 1); // где сейчас лежат единицы
std::swap(child[node][0], child[node][1]);
increase(child[node][1 - ones], level + 1);
}
Спуск идёт ровно по одной ветке на уровень, поэтому вся операция — логарифм, а не размер множества.
Ленивый xor
Применить xor с значит обменять детей у всех вершин тех уровней, где у стоит единица. Делать это честно дорого, но и не нужно: держим маску flip и при любом спуске читаем бит как bit ^ ((flip >> level) & 1).
Маска участвует и в прибавлении: понять, в каком ребёнке лежат единицы, без неё нельзя.
Как достать значение элемента
Бор хранит значения, а спрашивают обычно про индекс. Держите для каждого индекса указатель на его лист и поднимайтесь к корню, собирая биты: на каждом шаге видно, каким ребёнком является вершина. Обмен детей эту сторону меняет — значит при обмене её надо править, это две записи.
Бор удобно завести полным: все вершин созданы заранее, вставка ничего не создаёт, обмен — перестановка двух ссылок. При это два миллиона вершин, зато ни мусора, ни аллокаций.
Где ещё пригодится
Тот же бор по битам решает «наибольший xor с данным числом» (спуск в противоположную ветку), «сколько пар с xor меньше », «-й по величине xor». Глобальные операции к ним добавляются бесплатно — маска и обмены работают независимо от того, что именно вы спрашиваете.
Смежное: исключающее ИЛИ, бор, битовые трюки.