Este desafío es en parte un desafío de algoritmos, involucra algunas matemáticas y es en parte simplemente un desafío de código más rápido.
Para algún entero positivo n, considere una cadena aleatoria de forma uniforme 1s y 0s de longitud ny llamarlo A. Ahora también considere una segunda cadena aleatoria de longitud elegida uniformemente ncuyos valores son -1, 0,o 1y llámela B_pre. Ahora deja Bser B_pre+ B_pre. Eso se B_preconcatena a sí mismo.
Consideremos ahora el producto interno de Ay B[j,...,j+n-1]y llamarlo Z_jy el índice de 1.
Tarea
El resultado debe ser una lista de n+1fracciones. El iésimo término en la salida debe ser la exacta probabilidad de que todo de los primeros itérminos Z_jcon j <= iigual 0.
Puntuación
El más grande npara el que su código proporciona la salida correcta en menos de 10 minutos en mi máquina.
Desempate
Si dos respuestas tienen el mismo puntaje, gana la que presentó primero.
En el caso (muy, muy poco probable) de que alguien encuentre un método para obtener puntajes ilimitados, se aceptará la primera prueba válida de tal solución.
Insinuación
No intentes resolver este problema matemáticamente, es demasiado difícil. En mi opinión, la mejor manera es volver a las definiciones básicas de probabilidad de la escuela secundaria y encontrar formas inteligentes de obtener el código para hacer una enumeración exhaustiva de las posibilidades.
Idiomas y bibliotecas
Puede usar cualquier idioma que tenga un compilador / intérprete / etc. para Linux y cualquier biblioteca que también esté disponible gratuitamente para Linux.
Mi máquina Los tiempos se ejecutarán en mi máquina. Esta es una instalación estándar de ubuntu en un procesador AMD FX-8350 de ocho núcleos. Esto también significa que necesito poder ejecutar su código. Como consecuencia, solo use software gratuito fácilmente disponible e incluya instrucciones completas sobre cómo compilar y ejecutar su código.
Algunas salidas de prueba. Considere solo el primer resultado para cada uno n. Eso es cuando i=1. Para nde 1 a 13 que deberían ser.
1: 4/6
2: 18/36
3: 88/216
4: 454/1296
5: 2424/7776
6: 13236/46656
7: 73392/279936
8: 411462/1679616
9: 2325976/10077696
10: 13233628/60466176
11: 75682512/362797056
12: 434662684/2176782336
13: 2505229744/13060694016
También puede encontrar una fórmula general i=1en http://oeis.org/A081671 .
Tabla de clasificación (dividida por idioma)
- n = 15. Python + python paralelo + pypy en 1min49s por Jakube
- n = 17. C ++ en 3min37s por Keith Randall
- n = 16. C ++ en 2min38s por kuroi neko
fuente

Respuestas:
C ++, n = 18 en 9 min en 8 hilos
(Avíseme si dura menos de 10 minutos en su máquina).
Aprovecho varias formas de simetría en la matriz B. Esos son cíclicos (desplazamiento en una posición), inversión (invertir el orden de los elementos) y signo (tomar el negativo de cada elemento). Primero calculo la lista de Bs que necesitamos probar y su peso. Luego, cada B se ejecuta a través de una rutina rápida (usando instrucciones de conteo de bits) para todos los 2 ^ n valores de A.
Aquí está el resultado para n == 18:
Compile el siguiente programa con
g++ --std=c++11 -O3 -mpopcnt dot.ccfuente
-pthreadnuevo. Llego an=17mi máquina.Python 2 usando pypy y pp: n = 15 en 3 minutos
También solo una simple fuerza bruta. Es interesante ver que casi obtengo la misma velocidad que Kuroi Neko con C ++. Mi código puede alcanzar
n = 12en unos 5 minutos. Y solo lo ejecuto en un núcleo virtual.editar: reducir el espacio de búsqueda por un factor de
nNoté que un vector cíclico
A*deAproduce los mismos números que las probabilidades (los mismos números) que el vector originalAcuando repitoB. Ej El vector(1, 1, 0, 1, 0, 0)tiene las mismas probabilidades como cada uno de los vectores(1, 0, 1, 0, 0, 1),(0, 1, 0, 0, 1, 1),(1, 0, 0, 1, 1, 0),(0, 0, 1, 1, 0, 1)y(0, 1, 1, 0, 1, 0)al elegir una al azarB. Por lo tanto, no tengo que iterar sobre cada uno de estos 6 vectores, sino solo alrededor de 1 y reemplazarcount[i] += 1concount[i] += cycle_number.Esto reduce la complejidad de
Theta(n) = 6^naTheta(n) = 6^n / n. Porn = 13lo tanto , es aproximadamente 13 veces más rápido que mi versión anterior. Se calculan = 13en aproximadamente 2 minutos y 20 segundos. porn = 14todavía es un poco lento. Tarda unos 13 minutos.editar 2: programación multi-core
No estoy muy contento con la próxima mejora. Decidí también intentar ejecutar mi programa en múltiples núcleos. En mis núcleos 2 + 2 ahora puedo calcular
n = 14en aproximadamente 7 minutos. Solo un factor de mejora 2.El código está disponible en este repositorio de github: Link . La programación multinúcleo es un poco fea.
editar 3: Reducción del espacio de búsqueda para
Avectores yBvectoresNoté la misma simetría de espejo para los vectores
Aque Kuroi Neko. Todavía no estoy seguro, por qué esto funciona (y si funciona para cada unon).La reducción del espacio de búsqueda de
Bvectores es un poco más inteligente. Reemplacé la generación de los vectores (itertools.product), con una función propia. Básicamente, comienzo con una lista vacía y la pongo en una pila. Hasta que la pila esté vacía, elimino una lista, si no tiene la misma longitudn, genero otras 3 listas (agregando -1, 0, 1) y empujándolas a la pila. Si una lista tiene la misma longitud quen, puedo evaluar las sumas.Ahora que genero los vectores yo mismo, puedo filtrarlos dependiendo de si puedo alcanzar la suma = 0 o no. Por ejemplo, si mi vector
Aes(1, 1, 1, 0, 0), y mi vector seBve(1, 1, ?, ?, ?), lo sé, que no puedo llenar los?valores, de modo queA*B = 0. Así que no tengo que iterar sobre todos esos 6 vectoresBde la forma(1, 1, ?, ?, ?).Podemos mejorar esto si ignoramos los valores para 1. Como se señaló en la pregunta, los valores para
i = 1son la secuencia A081671 . Hay muchas formas de calcularlos. Elijo la repetición simple:a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n. Comoi = 1básicamente podemos calcular en poco tiempo, podemos filtrar más vectores paraB. Por ejemploA = (0, 1, 0, 1, 1)yB = (1, -1, ?, ?, ?). Podemos ignorar los vectores, donde los primeros? = 1, porque losA * cycled(B) > 0, para todos estos vectores. Espero que puedas seguir. Probablemente no sea el mejor ejemplo.Con esto puedo calcular
n = 15en 6 minutos.editar 4:
Implementé rápidamente la gran idea de kuroi neko, que dice eso
By-Bproduce los mismos resultados. Aceleración x2. Sin embargo, la implementación es solo un truco rápido.n = 15en 3 minutosCódigo:
Para ver el código completo, visite Github . El siguiente código es solo una representación de las características principales. Omití importaciones, programación multinúcleo, impresión de resultados, ...
Uso:
Tienes que instalar pypy (para Python 2 !!!). El módulo paralelo de Python no está portado para Python 3. Luego debe instalar el módulo paralelo de Python pp-1.6.4.zip . Extraerlo
cden la carpeta y llamarpypy setup.py install.Entonces puedes llamar a mi programa con
pypy you-do-the-math.py 15Determinará automáticamente el número de CPU. Puede haber algunos mensajes de error después de finalizar el programa, simplemente ignórelos.
n = 16debería ser posible en su máquina.Salida:
Notas e ideas:
A & By contar los bloques 01 y 10. El ciclismo se puede hacer cambiando el vector y usando máscaras, ... De hecho, implementé todo esto, puedes encontrarlo en algunos de mis compromisos más antiguos en Github. Pero resultó ser más lento que con las listas. Supongo que pypy realmente optimiza las operaciones de la lista.fuente
matón lanudo - C ++ - demasiado lento
Bueno, ya que un mejor programador asumió la implementación de C ++, estoy llamando a dejar de fumar para este.
Construyendo el ejecutable
Es una fuente independiente de C ++ 11 que se compila sin advertencias y se ejecuta sin problemas en:
Si compila con g ++, use: g ++ -O3 -pthread -std = c ++ 11
olvidarse
-pthreadproducirá un volcado de núcleo agradable y amigable.Optimizaciones
El último término Z es igual al primero (Bpre x A en ambos casos), por lo que los dos últimos resultados son siempre iguales, lo que dispensa el cálculo del último valor Z.
La ganancia es despreciable, pero codificarla no cuesta nada, por lo que bien podría usarla.
Como descubrió Jakube, todos los valores cíclicos de un vector A dado producen las mismas probabilidades.
Puede calcularlos con una sola instancia de A y multiplicar el resultado por el número de sus posibles rotaciones. Los grupos de rotación se pueden precalcular fácilmente en un tiempo descuidado, por lo que esta es una gran ganancia de velocidad neta.
Dado que el número de permutaciones de un vector de longitud n es n-1, la complejidad cae de o (6 n ) a o (6 n / (n-1)), básicamente yendo un paso más allá para el mismo tiempo de cálculo.
Parece que los pares de patrones simétricos también generan las mismas probabilidades. Por ejemplo, 100101 y 101001.
No tengo ninguna prueba matemática de eso, pero intuitivamente cuando se me presentan todos los patrones B posibles, cada valor A simétrico se enredará con el valor B simétrico correspondiente para el mismo resultado global.
Esto permite reagrupar algunos vectores A más, para una reducción aproximada del 30% del número de grupos A.
Incorrecto
Por alguna razón semi-misteriosa, todos los patrones con solo uno o dos bits establecidos producen el mismo resultado. Esto no representa tantos grupos distintos, pero aun así pueden fusionarse prácticamente sin costo.Los vectores B y -B (B con todos los componentes multiplicados por -1) producen las mismas probabilidades.
(por ejemplo [1, 0, -1, 1] y [-1, 0, 1, -1]).
Excepto por el vector nulo (todos los componentes son iguales a 0), B y -B forman un par de vectores distintos .
Esto permite reducir a la mitad el número de valores de B considerando solo uno de cada par y multiplicando su contribución por 2, agregando la contribución global conocida de B nulo a cada probabilidad solo una vez.
Cómo funciona
El número de valores B es enorme (3 n ), por lo que precalcularlos requeriría cantidades indecentes de memoria, lo que ralentizaría el cálculo y eventualmente agotaría la RAM disponible.
Desafortunadamente, no pude encontrar una manera simple de enumerar el medio conjunto de valores B optimizados, así que recurrí a la codificación de un generador dedicado.
El poderoso generador B fue muy divertido de codificar, aunque los lenguajes que admiten mecanismos de rendimiento habrían permitido programarlo de una manera mucho más elegante.
Lo que hace en pocas palabras es considerar el "esqueleto" de un vector Bpre como un vector binario donde los 1 representan valores reales de -1 o +1.
Entre todos estos valores potenciales + 1 / -1, el primero se fija en +1 (seleccionando así uno de los posibles vectores B / -B), y se enumeran todas las combinaciones posibles + 1 / -1 restantes.
Por último, un sistema de calibración simple garantiza que cada hilo de trabajo procesará un rango de valores de aproximadamente el mismo tamaño.
Los valores A están muy filtrados para reagruparse en trozos equiprobables.
Esto se hace en una fase previa a la computación que la fuerza bruta examina todos los valores posibles.
Esta parte tiene un tiempo de ejecución de O (2 n ) descuidado y no necesita ser optimizado (¡el código ya es lo suficientemente ilegible como es!).
Para evaluar el producto interno (que solo necesita ser probado contra cero), los componentes -1 y 1 de B se reagrupan en vectores binarios.
El producto interno es nulo si (y solo si) hay un número igual de + 1s y -1s entre los valores B correspondientes a valores A distintos de cero.
Esto se puede calcular con operaciones simples de enmascaramiento y conteo de bits, ayudado por
std::bitseteso producirá un código de conteo de bits razonablemente eficiente sin tener que recurrir a instrucciones intrínsecas feas.El trabajo se divide por igual entre los núcleos, con afinidad forzada de la CPU (cada poquito ayuda, o eso dicen).
Resultado de ejemplo
Actuaciones
Multithreading debería funcionar perfectamente en esto, aunque solo los núcleos "verdaderos" contribuirán completamente a la velocidad de cálculo. Mi CPU tiene solo 2 núcleos para 4 CPU, y la ganancia sobre una versión de subproceso único es "solo" aproximadamente 3.5.
Compiladores
Un problema inicial con subprocesos múltiples me llevó a creer que los compiladores de GNU estaban funcionando peor que Microsoft.
Después de pruebas más exhaustivas, parece que g ++ gana el día una vez más, produciendo un código aproximadamente un 30% más rápido (la misma proporción que noté en otros dos proyectos pesados en computación).
En particular, la
std::bitsetbiblioteca se implementa con instrucciones de recuento de bits dedicadas por g ++ 4.8, mientras que MSVC 2013 usa solo bucles de cambios de bits convencionales.Como era de esperar, compilar en 32 o 64 bits no hace ninguna diferencia.
Más refinamientos
Noté algunos grupos A que producen las mismas probabilidades después de todas las operaciones de reducción, pero no pude identificar un patrón que permitiera reagruparlos.
Aquí están los pares que noté para n = 11:
fuente
terminate called after throwing an instance of 'std::system_error' what(): Unknown error -1 Aborted (core dumped)