Tutorial
Ideias com XOR
Aplicações comuns e não triviais do XOR
Introdução
Definições
XOR, representado por , é uma operação binária que pode ser definida a partir da seguinte tabela:
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Esta operação pode ser naturalmente extendida para inteiros maiores que : Basta considerar a representação binária dos números, e fazer a operação para cada bit respectivamente.
Exemplo: .
Esta extensão da operação de XOR é chamada de bitwise XOR, ou “XOR para cada bit”.
Em C++, a operação é feita com o operador ^.
Propriedades
A operação de XOR é comutativa e associativa, ou seja,
para quaisquer , e . Além disso, temos que
Ideias
1. Cancelando valores iguais
Uma aplicação direta do fato que é que se temos o XOR de alguns elementos , e queremos apenas , por exemplo, podemos simplesmente calcular o XOR com :
Com isso, podemos fazer algo semelhante a soma de prefixos com XOR, por exemplo.
Problema. Você recebe um array de inteiros onde cada elemento aparece exatamente duas vezes no array, exceto um único elemento, que aparece exatamente uma vez. Encontre o elemento que aparece apenas uma vez.
Aqui, nós abusamos do fato que o XOR é comutativo, e que . Se calcularmos o XOR de todos os números do array, cada número que aparece duas vezes vai ser cancelado, e o que vai sobrar é exatamente o elemento que aparece uma vez.
2. Hashing com XOR
A ideia é usar a comutatividade do xor para verificar se dois conjuntos tem os mesmos elementos. Se dois conjuntos tem os mesmos elementos, o XOR deles também é igual.
Há um problema com essa abordagem: imagine que queremos verificar se é igual a . Ambos os conjuntos tem XOR , então o algoritmo diria que os conjuntos são iguais.
Uma forma de resolver esse problema é a seguinte:
- Não calcular o XOR dos números diretamente.
- Em vez disso, manter um map para associar cada número possível de um conjunto com um número aleatório.
- Na hora de calcular o XOR, não usamos os números do conjunto em si, e sim os números mapeados.
- Com isso, a probabilidade de dois conjuntos diferentes terem o mesmo XOR é reduzida significativamente.
Problema. Você recebe uma permutação dos números , e você precisa processar queries da seguinte forma: dados , , verifique se os números são números consecutivos em alguma ordem. Por exemplo, os números são números consecutivos () permutados.
Vamos aplicar a ideia acima. Para isso, precisamos saber qual o conjunto de números deveria estar no intervalo. Podemos usar qualquer estrutura para encontrar o mínimo e o máximo em um intervalo, como uma sparse table ou uma segment tree. Dados o mínimo e o máximo, digamos e , os números devem ser .
Agora vem a parte do XOR. Para cada número , vamos associar um número aleatório, e precisamos calcular o XOR dos números associados a de forma eficiente, e podemos fazer isso com soma de prefixos com XOR. Finalmente, só precisamos calcular o XOR dos números associados a elementos do array original em um intervalo, que também podemos fazer com soma de prefixos com XOR.
Se usarmos sparse table para encontrar o mínimo e o máximo, a complexidade final fica por query.