Исключающее «или»
Почему XOR встречается в задачах чаще остальных операций: обратимость, связь со сложением и жадность по старшему биту.
3 мин
У исключающего «или» есть свойства, которых нет у «и» и «или», и из-за них оно встречается в задачах гораздо чаще.
| свойство | запись |
|---|---|
| само себе обратно | |
| коммутативно | |
| ассоциативно | |
| нейтральный элемент | |
| каждый элемент обратен себе |
Последние три означают, что числа с операцией XOR образуют группу, причём каждый элемент имеет порядок два. Это и есть векторное пространство над полем из двух элементов — отсюда линейный базис.
Напарник определён однозначно
Из обратимости: если , то . Значит, задача «найти пары с заданным XOR» — это не перебор пар, а поиск:
long long answer = 0;
std::unordered_map<int, int> seen;
for (int x : a) { answer += seen[x ^ k]; seen[x]++; }
Тот же приём: «найти единственный элемент, встречающийся один раз, когда все остальные по два» — это XOR всего массива. И «найти пропущенное число» — XOR всех чисел от 1 до n с XOR массива.
Связь со сложением
Сложение отличается от исключающего «или» ровно переносами, а переносы порождаются общими битами. Из этого тождества выводится половина задач про XOR.
Например, уравнение равносильно ; подставив тождество, получаем , то есть — подмаска . Решений ровно .
Ещё следствие: всегда, и равенство достигается тогда и только тогда, когда у чисел нет общих битов.
Жадность по старшему биту
Старший бит важнее всех младших вместе взятых: . Поэтому задачи «максимизировать XOR» решаются жадно сверху вниз: если очередной бит можно сделать единицей, это надо сделать.
Проверка «можно ли» делается множеством префиксов или бором по битам:
int node = root, answer = 0;
for (int bit = 29; bit >= 0; bit--) {
int want = ((x >> bit) & 1) ^ 1; // хотим противоположный бит
if (child[node][want]) { answer |= 1 << bit; node = child[node][want]; }
else node = child[node][want ^ 1];
}
Важно: жадность с младшего бита неверна. Проверено перебором: на 120 000 случайных наборов из нескольких чисел до шести битов такая жадность разошлась с настоящим максимумом в 35 934 случаях. Например, на наборе она даёт 3 вместо 6.