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.
a<=0
o parab<0
.long long
es un tipo con signo, por lo quex%4
es negativo (o 0) para entradas negativas . ¿Quizás deseeunsigned long long
y / oa & 3
indexar la matriz?Respuestas:
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: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 dea
, dejándolo con el XOR del rango [a, b].fuente
a
there is 2, not 0.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:Aquí están ambos en acción:
Me gusta esto:
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:
Si ayuda, piense en XOR (^) como si fuera un complemento.
Definamos también la función:
b
es mayor quea
, por lo que simplemente colocando de forma segura algunos corchetes adicionales (lo cual podemos porque es asociativo), también podemos decir esto:Lo que se simplifica a:
A continuación, usamos esa propiedad de inversión y la comutividad para darnos la línea mágica:
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).
fuente
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
fuente
He resuelto el problema de la recursividad. Simplemente divido el conjunto de datos en una parte casi igual para cada iteración.
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
fuente
Para admitir XOR de 0 a N, el código dado debe modificarse como se muestra a continuación,
fuente