Tabla de clasificación
User Language Score
=========================================
Ell C++11 293,619,555
feersum C++11 100,993,667
Ell C++11 78,824,732
Geobits Java 27,817,255
Ell Python 27,797,402
Peter Taylor Java 2,468
<reference> Julia 530
Antecedentes
Cuando se trabaja en una cuadrícula 2D de coordenadas enteras, a veces desea saber si dos vectores (con componentes enteros) tienen la misma magnitud. Por supuesto, en geometría euclidiana, la magnitud de un vector (x,y)
viene dada por
√(x² + y²)
Entonces, una implementación ingenua podría calcular este valor para ambos vectores y comparar los resultados. Esto no solo genera un cálculo innecesario de raíz cuadrada, sino que también causa problemas con las imprecisiones de coma flotante, lo que puede generar falsos positivos: vectores cuyas magnitudes son diferentes, pero donde los dígitos significativos en la representación de coma flotante son todos idénticos.
Para los propósitos de este desafío, definimos un falso positivo como un par de pares de coordenadas (a,b)
y (c,d)
para el cual:
- Su magnitud al cuadrado es diferente cuando se representan como enteros sin signo de 64 bits.
- Su magnitud es idéntica cuando se representa como un número de punto flotante binario de 64 bits y se calcula a través de una raíz cuadrada de 64 bits (según IEEE 754 ).
Como ejemplo, usando representaciones de 16 bits (en lugar de 64), el 1 par más pequeño de vectores que produce un falso positivo es
(25,20) and (32,0)
Sus magnitudes al cuadrado al cuadrado son 1025
y 1024
. Tomando los rendimientos de raíz cuadrada
32.01562118716424 and 32.0
Pero en flotantes de 16 bits, ambos se truncan 32.0
.
Del mismo modo, el par más pequeño de 2 que produce un falso positivo para representaciones de 32 bits es
(1659,1220) and (1951,659)
1 "Más pequeño" medido por su magnitud de coma flotante de 16 bits.
2 "Más pequeño" medido por su magnitud de coma flotante de 32 bits.
Por último, aquí hay un puñado de casos válidos de 64 bits:
(51594363,51594339) and (54792160,48184783)
(54356775,54353746) and (54620742,54088476)
(54197313,46971217) and (51758889,49645356)
(67102042, 956863) and (67108864, 6) *
*
El último caso es uno de varios con la menor magnitud posible para falsos positivos de 64 bits.
El reto
En menos de 10,000 bytes de código, usando un solo hilo, encontrará tantos falsos positivos para números de punto flotante (binario) de 64 bits en el rango de coordenadas 0 ≤ y ≤ x
(es decir, solo dentro del primer octante del plano euclidiano) tal que dentro de los 10 minutos . Si dos envíos empatan para la misma cantidad de pares, el desempate es el tiempo real necesario para encontrar el último de esos pares.x² + y² ≤ 253
Su programa no debe usar más de 4 GB de memoria en ningún momento (por razones prácticas).
Debe ser posible ejecutar su programa en dos modos: uno que genera cada par a medida que lo encuentra, y otro que solo genera el número de pares encontrados al final. El primero se usará para verificar la validez de sus pares (observando alguna muestra de resultados) y el segundo se usará para realmente sincronizar su envío. Tenga en cuenta que la impresión debe ser la única diferencia. En particular, el programa de conteo puede no codificar el número de pares que podría encontrar. ¡Todavía debe realizar el mismo bucle que se usaría para imprimir todos los números, y solo omitir la impresión misma!
Probaré todas las presentaciones en mi computadora portátil con Windows 8, así que pregunte en los comentarios si desea usar un lenguaje no muy común.
Tenga en cuenta que los pares no se deben contar dos veces al cambiar el primer y el segundo par de coordenadas.
Tenga en cuenta también que ejecutaré su proceso a través de un controlador Ruby, que matará su proceso si no ha terminado después de 10 minutos. Asegúrese de generar el número de pares encontrados para entonces. Puede realizar un seguimiento del tiempo usted mismo e imprimir el resultado justo antes de que transcurran los 10 minutos, o simplemente generar el número de pares encontrados esporádicamente, y tomaré el último número como su puntaje.
fuente
Respuestas:
C ++, 275,000,000+
Nos referiremos a pares cuya magnitud es representable con precisión, como (x, 0) , como pares honestos y a todos los demás pares como pares deshonestos de magnitud m , donde m es la magnitud informada erróneamente del par. El primer programa en la publicación anterior usó un conjunto de parejas estrechamente relacionadas de pares honestos y deshonestos:
(x, 0) y (x, 1) , respectivamente, para x suficientemente grande. El segundo programa usó el mismo conjunto de pares deshonestos pero extendió el conjunto de pares honestos al buscar todos los pares honestos de magnitud integral. El programa no finaliza en diez minutos, pero encuentra la gran mayoría de sus resultados muy pronto, lo que significa que la mayor parte del tiempo de ejecución se desperdicia. En lugar de seguir buscando pares honestos cada vez menos frecuentes, este programa usa el tiempo libre para hacer la siguiente cosa lógica: extender el conjunto de pares deshonestos .
De la publicación anterior, sabemos que para todos los enteros suficientemente grandes r , sqrt (r 2 + 1) = r , donde sqrt es la función de raíz cuadrada de punto flotante. Nuestro plan de ataque es encontrar pares P = (x, y) de modo que x 2 + y 2 = r 2 + 1 para algún número entero suficientemente grande r . Eso es lo suficientemente simple como para hacerlo, pero la ingenua búsqueda de pares individuales es demasiado lenta para ser interesante. Queremos encontrar estos pares a granel, tal como lo hicimos para los pares honestos en el programa anterior.
Sea { v , w } un par de vectores ortonormales. Para todos los escalares reales r , || r v + w || 2 = r 2 + 1 . En ℝ 2 , este es un resultado directo del teorema de Pitágoras:
Estamos buscando los vectores v y w de modo que exista un número entero r para el cual x e y también sean números enteros. Como nota al margen, tenga en cuenta que el conjunto de pares deshonestos que utilizamos en los dos programas anteriores fue simplemente un caso especial de esto, donde { v , w } era la base estándar de ℝ 2 ; Esta vez deseamos encontrar una solución más general. Aquí es donde los trillizos pitagóricos (trillizos enteros (a, b, c) que satisfacen a 2 + b 2 = c 2, que utilizamos en el programa anterior) hacen su regreso.
Sea (a, b, c) un triplete pitagórico. Los vectores v = (b / c, a / c) y w = (-a / c, b / c) (y también
w = (a / c, -b / c) ) son ortonormales, como es fácil de verificar . Resulta que, para cualquier elección del triplete pitagórico, existe un número entero r tal que x e y son números enteros. Para probar esto, y para encontrar efectivamente r y P , necesitamos una pequeña teoría de números / grupos; Voy a ahorrar los detalles. De cualquier manera, supongamos que tenemos nuestra integral r , x e y . Todavía nos faltan algunas cosas: necesitamos rser lo suficientemente grande y queremos un método rápido para derivar muchos más pares similares de este. Afortunadamente, hay una manera simple de lograr esto.
Tenga en cuenta que la proyección de P en v es r v , por lo tanto, r = P · v = (x, y) · (b / c, a / c) = xb / c + ya / c , todo esto para decir que xb + ya = rc . Como resultado, para todos los enteros n , (x + bn) 2 + (y + an) 2 = (x 2 + y 2 ) + 2 (xb + ya) n + (a 2 + b 2 ) n 2 = ( r 2 + 1) + 2 (rc) n + (c 2 ) n 2 = (r + cn) 2 + 1. En otras palabras, la magnitud al cuadrado de los pares de la forma
(x + bn, y + an) es (r + cn) 2 + 1 , ¡que es exactamente el tipo de pares que estamos buscando! Para n suficientemente grande , estos son pares deshonestos de magnitud r + cn .
Siempre es bueno mirar un ejemplo concreto. Si tomamos el triplete pitagórico (3, 4, 5) , entonces en r = 2 tenemos P = (1, 2) (puede verificar que (1, 2) · (4/5, 3/5) = 2 y, claramente, 1 2 + 2 2 = 2 2 + 1. ) Sumar 5 a r y (4, 3) a P nos lleva a r '= 2 + 5 = 7 y P' = (1 + 4, 2 + 3) = (5, 5) . Lo y he aquí, 5 2 + 5 2 = 7 2 + 1. Las siguientes coordenadas son r '' = 12 y P '' = (9, 8) , y nuevamente, 9 2 + 8 2 = 12 2 + 1 , y así sucesivamente ...
Una vez que r es lo suficientemente grande, comenzamos a obtener pares deshonestos con incrementos de magnitud de 5 . Eso es aproximadamente 27,797,402 / 5 pares deshonestos.
Así que ahora tenemos muchos pares deshonestos de magnitud integral. Podemos unirlos fácilmente con los pares honestos del primer programa para formar falsos positivos, y con el debido cuidado también podemos usar los pares honestos del segundo programa. Esto es básicamente lo que hace este programa. Al igual que el programa anterior, también encuentra la mayoría de sus resultados muy pronto, llega a 200,000,000 de falsos positivos en unos pocos segundos, y luego se ralentiza considerablemente.
Compilar con
g++ flspos.cpp -oflspos -std=c++11 -msse2 -mfpmath=sse -O3
. Para verificar los resultados, agregue-DVERIFY
(esto será notablemente más lento).Corre con
flspos
. Cualquier argumento de línea de comandos para el modo detallado.fuente
Pitón, 27,797,402
Solo para poner el listón un poco más alto ...
Es fácil verificar que para todos los 67,108,864 <= x <= 94,906,265 = floor (sqrt (2 53 )) los pares (x, 0) y (x, 1) son falsos positivos.
Por qué funciona : 67,108,864 = 2 26 . Por lo tanto, todos los números x en el rango anterior tienen la forma 2 26 + x ' para algunos 0 <= x' <2 26 . Para todos los positivos e , (x + e) 2 = x 2 + 2xe + e 2 = x 2 + 2 27 e + 2x'e + e 2 . Si queremos tener
(x + e) 2 = x 2 + 1 necesitamos al menos 2 27 e <= 1 , es decir, e <= 2 -27 Sin embargo, dado que la mantisa de los números de coma flotante de doble precisión es de 52 bits de ancho, la más pequeña e tal que x + e> x es e = 2 26 - 52 = 2 -26 . En otras palabras, el número representable más pequeño mayor que x es x + 2 -26, mientras que el resultado de sqrt (x 2 + 1) es como máximo x + 2 -27 . Dado que el modo de redondeo IEEE-754 predeterminado es redondear al más cercano; lazos a pares, siempre se redondeará a x y nunca a x + 2 -26 (donde el desempate solo es relevante para x = 67,108,864, como mucho. Cualquier número mayor se redondeará a x independientemente).
C ++, 75,000,000+
Recuerde que 3 2 + 4 2 = 5 2 . Lo que esto significa es que el punto (4, 3) se encuentra en el círculo de radio 5 centrado alrededor del origen. En realidad, para todos los enteros n , (4n, 3n) se encuentra en dicho círculo de radio 5n . Para n suficientemente grande (es decir, tal que 5n> = 2 26 ), ya conocemos un falso positivo para todos los puntos en este círculo: (5n, 1) . ¡Excelente! ¡Eso es otros 27,797,402 / 5 pares de falsos positivos gratis! ¿Pero por qué parar aquí? (3, 4, 5) no es el único triplete.
Este programa busca todos los tripletes enteros positivos (a, b, c) de modo que a 2 + b 2 = c 2 , y cuenta los falsos positivos de esta manera. Llega a 70,000,000 falsos positivos bastante rápido, pero luego disminuye considerablemente a medida que aumentan los números.
Compilar con
g++ flspos.cpp -oflspos -std=c++11 -msse2 -mfpmath=sse -O3
. Para verificar los resultados, agregue-DVERIFY
(esto será notablemente más lento).Corre con
flspos
. Cualquier argumento de línea de comandos para el modo detallado.fuente
2**53
se eligió el límite para descartar esto, pero supongo que no.C ++ 11 - 100,993,667
EDITAR: Nuevo programa.
El viejo usaba demasiada memoria. Este reduce a la mitad el uso de memoria mediante el uso de una matriz de vectores gigantes en lugar de una tabla hash. También elimina el hilo aleatorio cruft.
Ejecute con un
-P
argumento para imprimir los puntos en lugar del número de los mismos.Para mí, lleva menos de 2 minutos en el modo de conteo y aproximadamente 5 minutos con la impresión dirigida a un archivo (~ 4 GB), por lo que no se limitó a E / S.
Mi programa original estaba ordenado, pero dejé caer la mayor parte ya que solo podía producir en el orden de 10 ^ 5 resultados. Lo que hizo fue buscar parametrizaciones de la forma (x ^ 2 + Ax + B, x ^ 2 + Cx + D), (x ^ 2 + ax + b, x ^ 2 + cx + d) de modo que para cualquier x, (x ^ 2 + Ax + B) ^ 2 + (x ^ 2 + Cx + D) ^ 2 = (x ^ 2 + ax + b) ^ 2 + (x ^ 2 + cx + d) ^ 2 + 1. Cuando encontró tal conjunto de parámetros {a, b, c, d, A, B, C, D}, procedió a verificar todos los valores de x por debajo del máximo. Mientras miraba mi salida de depuración de este programa, noté una cierta parametrización de la parametrización de la parametrización que me permitió producir muchos números fácilmente. Elegí no imprimir los números de Ell ya que tenía muchos. Esperemos que ahora alguien no imprima nuestros dos conjuntos de números y afirme ser el ganador :)
fuente
g++ (GCC) 4.8.1
. De acuerdo, eliminé los bits de hilo, pero todavía no se reconocestricmp
por alguna razón.Escaneo circular Java, Bresenham-esque
Heurísticamente espero obtener más colisiones comenzando en el extremo más ancho del anillo. Esperaba obtener alguna mejora haciendo un escaneo para cada colisión, registrando valores para los que
surplus
está entre0
er2max - r2
inclusive, pero en mis pruebas resultó ser más lento que esta versión. Del mismo modo, intenta utilizar un únicoint[]
búfer en lugar de mucha creación de matrices y listas de dos elementos. La optimización del rendimiento es realmente una bestia extraña.Ejecute con un argumento de línea de comandos para la salida de los pares, y sin recuentos simples.
fuente
Java - 27,817,255
La mayoría de estos son los mismos que muestra Ell , y el resto se basa en
(j,0) (k,l)
. Para cada unoj
, retrocedo algunos cuadrados y verifico si el resto da un falso positivo. Esto ocupa básicamente todo el tiempo con solo una ganancia de 25k (aproximadamente 0.1%) sobre solo(j,0) (j,1)
, pero una ganancia es una ganancia.Esto terminará en menos de diez minutos en mi máquina, pero no sé lo que tienes. Por razones, si no termina antes de que acabe el tiempo, tendrá un puntaje drásticamente peor. En ese caso, puede ajustar el divisor en la línea 8 para que termine en el tiempo (esto simplemente determina qué tan atrás camina para cada uno
j
). Para algunos divisores diferentes, los puntajes son:Para activar la salida para cada partido (y, dios, es lento si lo hace), solo descomente las líneas 10 y 19.
Como referencia, las primeras 20 salidas que da (para divisor = 7, excluyendo
(j,0)(j,1)
tipos) son:fuente
Julia, 530 falsos positivos
Aquí hay una búsqueda de fuerza bruta muy ingenua, que puede ver como una implementación de referencia.
Puede imprimir los pares (y sus magnitudes cuadradas exactas) descomentando la
@printf
línea.Básicamente, esto inicia la búsqueda
x = y = 6e7
del primer par de coordenadas y escanea aproximadamente el 1% del camino hasta el eje x antes de disminuir x. Luego, para cada par de coordenadas, verifica el arco completo de la misma magnitud (redondeando hacia arriba y hacia abajo) en busca de una colisión.El código supone que se ejecuta en un sistema de 64 bits, de modo que los tipos enteros y de coma flotante predeterminados son de 64 bits (si no, puede crearlos con
int64()
yfloat64()
constructores).Eso arroja unos escasos 530 resultados.
fuente