Encuentra XOR de todos los números en un rango dado

99

Se le da un rango amplio [a, b] donde 'a' y 'b' pueden estar típicamente entre 1 y 4,000,000,000 inclusive. Tienes que averiguar el XOR de todos los números en el rango dado.

Este problema se utilizó en TopCoder SRM. Vi una de las soluciones enviadas en el partido y no puedo entender cómo funciona.

¿Alguien podría ayudar a explicar la solución ganadora?

long long f(long long a) {
     long long res[] = {a,1,a+1,0};
     return res[a%4];
}

long long getXor(long long a, long long b) {
     return f(b)^f(a-1);
}

Aquí, getXor()es la función real para calcular el xor de todos los números en el rango pasado [a, b] y "f ()" es una función auxiliar.

rajneesh2k10
fuente
Edité tu pregunta solo un poco. No nos importa explicar el por qué de algún código, pero no necesitamos una nueva lista de otras formas de resolver esto. Deje eso a TopCoder.
Kev
@Kev ¡Sin problemas! Escribí eso porque a algunas personas les encanta dar su propio camino en lugar de explicar lo que ya está escrito. Y cualquier idea nueva nunca es un desperdicio ...;)
rajneesh2k10
Esto tiene un comportamiento indefinido para a<=0o para b<0. long longes un tipo con signo, por lo que x%4es negativo (o 0) para entradas negativas . ¿Quizás desee unsigned long longy / o a & 3indexar la matriz?
Peter Cordes

Respuestas:

152

Esta es una solución bastante inteligente: aprovecha el hecho de que hay un patrón de resultados en los XOR en ejecución. La f()función calcula la ejecución total de XOR a partir de [0, a]. Eche un vistazo a esta tabla para números de 4 bits:

0000 <- 0  [a]
0001 <- 1  [1]
0010 <- 3  [a+1]
0011 <- 0  [0]
0100 <- 4  [a]
0101 <- 1  [1]
0110 <- 7  [a+1]
0111 <- 0  [0]
1000 <- 8  [a]
1001 <- 1  [1]
1010 <- 11 [a+1]
1011 <- 0  [0]
1100 <- 12 [a]
1101 <- 1  [1]
1110 <- 15 [a+1]
1111 <- 0  [0]

Donde la primera columna es la representación binaria y luego el resultado decimal y su relación con su índice (a) en la lista XOR. Esto sucede porque todos los bits superiores se cancelan y los dos bits más bajos tienen un ciclo cada 4. Entonces, así es como llegar a esa pequeña tabla de búsqueda.

Ahora, considere un rango general de [a, b]. Podemos usar f()para encontrar el XOR para [0, a-1] y [0, b]. Dado que cualquier valor XOR con él mismo es cero, f(a-1)simplemente cancela todos los valores en el XOR ejecutar menos de a, dejándolo con el XOR del rango [a, b].

Error fatal
fuente
el umbral de rango mínimo es 1, no 0
Pencho Ilchev
2
@PenchoIlchev Ya sea que incluya 0 o no es algo discutible - (n ^ 0) == n
FatalError
2
@ rajneesh2k10 Bueno, en corridas de 4 (comenzando en un múltiplo de 4), todos los bits excepto el más bajo son iguales, por lo que alternan entre cancelarse entre sí o tener su valor original. Es cierto que el bit más bajo cicla cada 2, pero 0 ^ 1 == 1 (es decir, no se cancelan). La razón por la que los dos más bajos son especiales es porque (00 ^ 01 ^ 10 ^ 11) == 00. En otras palabras, cada 4 valores que recorre lo devuelve a 0, por lo que puede cancelar todos esos ciclos, que es por qué un% 4 es significativo.
FatalError
3
@Pandrei the athere is 2, not 0.
harold
1
Esa columna es el xor y 1 xor 2 es 3, por lo que el valor actual en esa fila me parece correcto.
FatalError
58

Agregando a la gran respuesta de FatalError, la línea return f(b)^f(a-1);podría explicarse mejor. En resumen, es porque XOR tiene estas maravillosas propiedades:

  • Es asociativo : coloca los corchetes donde quieras
  • Es conmutativo , lo que significa que puede mover a los operadores (pueden "conmutar")

Aquí están ambos en acción:

(a ^ b ^ c) ^ (d ^ e ^ f) = (f ^ e) ^ (d ^ a ^ b) ^ c
  • Se invierte

Me gusta esto:

a ^ b = c
c ^ a = b

Sumar y multiplicar son dos ejemplos de otros operadores asociativos / conmutativos, pero no se invierten. Bien, entonces, ¿por qué son importantes estas propiedades? Bueno, una ruta simple es expandirlo hasta convertirlo en lo que realmente es, y luego podrá ver estas propiedades en funcionamiento.

Primero, definamos lo que queremos y llamémoslo n:

n      = (a ^ a+1 ^ a+2 .. ^ b)

Si ayuda, piense en XOR (^) como si fuera un complemento.

Definamos también la función:

f(b)   = 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ b

bes mayor que a, por lo que simplemente colocando de forma segura algunos corchetes adicionales (lo cual podemos porque es asociativo), también podemos decir esto:

f(b)   = ( 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ (a-1) ) ^ (a ^ a+1 ^ a+2 .. ^ b)

Lo que se simplifica a:

f(b)   = f(a-1) ^ (a ^ a+1 ^ a+2 .. ^ b)

f(b)   = f(a-1) ^ n

A continuación, usamos esa propiedad de inversión y la comutividad para darnos la línea mágica:

n      = f(b) ^ f(a-1)

Si ha estado pensando en XOR como una suma, habría dejado caer una resta allí. ¡XOR es XOR lo que sumar es restar!

¿Cómo se me ocurre esto yo mismo?

Recuerde las propiedades de los operadores lógicos. Trabaje con ellos casi como una suma o una multiplicación si ayuda. Se siente inusual que y (&), xor (^) yo (|) sean asociativos, ¡pero lo son!

Ejecute primero la implementación ingenua, busque patrones en la salida y luego comience a encontrar reglas que confirmen que el patrón es verdadero. Simplifique su implementación aún más y repita. Esta es probablemente la ruta que tomó el creador original, resaltada por el hecho de que no es completamente óptima (es decir, use una declaración de cambio en lugar de una matriz).

Luke Briggs
fuente
3
Esto me recuerda a mi curso de Matemáticas Discretas que tomé el año pasado en la universidad. Días divertidos. Lo que me vino a la mente inmediatamente después de leer esto es este cómic de XKCD .
Sean Francis N. Ballais
3

Descubrí que el siguiente código también funciona como la solución dada en la pregunta.

Puede ser que esto esté poco optimizado, pero es justo lo que obtuve al observar la repetición como se indica en la respuesta aceptada,

Me gustaría saber / comprender la prueba matemática detrás del código dado, como se explica en la respuesta de @Luke Briggs

Aquí está ese código JAVA

public int findXORofRange(int m, int n) {
    int[] patternTracker;

    if(m % 2 == 0)
        patternTracker = new int[] {n, 1, n^1, 0};
    else
        patternTracker = new int[] {m, m^n, m-1, (m-1)^n};

    return patternTracker[(n-m) % 4];
}
Parth Vishvajit
fuente
2

He resuelto el problema de la recursividad. Simplemente divido el conjunto de datos en una parte casi igual para cada iteración.

public int recursion(int M, int N) {
    if (N - M == 1) {
        return M ^ N;
    } else {
        int pivot = this.calculatePivot(M, N);
        if (pivot + 1 == N) {
            return this.recursion(M, pivot) ^ N;
        } else {
            return this.recursion(M, pivot) ^ this.recursion(pivot + 1, N);
        }
    }
}
public int calculatePivot(int M, int N) {
    return (M + N) / 2;
}

Déjame saber tus pensamientos sobre la solución. Feliz de recibir comentarios de mejora. La solución propuesta calcula el XOR en complejidad 0 (log N).

Gracias

Abhijeet Sonawane
fuente
Este tiene la misma complejidad computacional con el cálculo normal m ^ (m + 1) ^ ... ^ (n-1) ^ n. Este es 0 (n).
Thế Anh Nguyễn
0

Para admitir XOR de 0 a N, el código dado debe modificarse como se muestra a continuación,

int f(int a) {
    int []res = {a, 1, a+1, 0};
    return res[a % 4];
}

int getXor(int a, int b) {
    return f(b) ^ f(a);
}
Mohammad Nazmul Hossain
fuente