El period
de una cadena es el desplazamiento más corto distinto de cero, de modo que la cadena coincide con sí misma, ignorando cualquier parte que sobresalga. Entonces, por ejemplo, abcabcab
tiene punto 3
. Por convención, decimos que si no existe tal cambio, entonces una cadena tiene un período igual a su longitud. Entonces, el período de abcde
es 5
y el período de a
es 1
.
En términos más formales, el período de una cadena S
es el mínimo i > 0
para que S[1,n-i] == S[i+1,n]
(indexando desde 1
).
Para una cadena S dada de potencia de dos longitudes, calcularemos el período de todos sus prefijos de potencia de dos longitudes. Por ejemplo, considere S = abcabcab
. Los períodos que calcularemos son:
'a', 1
'ab', 2
'abca', 3
'abcabcab', 3
De hecho, solo mostraremos el conjunto de períodos, es decir [1, 2, 3, 3]
.
Para una potencia positiva dada de dos n
, considere todas las cadenas binarias posibles S
. Recuerde que una cadena binaria es simplemente una cadena de 1
sys, 0
por lo que existen exactamente 2^n
esas cadenas (eso es lo que tiene que ver 2
con la potencia n
). Para cada uno podemos calcular este conjunto de períodos.
El desafío es escribir código que tome
n
(una potencia de dos) como entrada y calcule cuántas matrices distintas existen.
Las respuestas para n = 1, 2, 4, 8, 16, 32, 64, 128
son:
1, 2, 6, 32, 320, 6025, 216854, 15128807
El conjunto completo de matrices de períodos distintos para n = 4
es:
1, 1, 1
1, 1, 3
1, 1, 4
1, 2, 2
1, 2, 3
1, 2, 4
Puntuación
Ejecutaré su código en mi computadora con Ubuntu durante 10 minutos. Su puntaje es el mayor n
por el cual su código termina en ese momento. En el caso de un empate, la respuesta que completa la mayor n
victoria conjunta . En el caso de que haya un empate dentro de 1 segundo en los tiempos, la primera respuesta publicada gana.
Idiomas y bibliotecas
Puede usar cualquier idioma y bibliotecas disponibles que desee. Incluya una explicación completa sobre cómo ejecutar / compilar su código en Linux, si es posible.
Su código realmente debería calcular las respuestas y no, por ejemplo, solo generar valores precalculados.
Entradas principales
- 2 minutos y 21 segundos para n = 128 en C # por Peter Taylor
- 9 segundos para n = 32 en óxido por isaacg
n
, ¿lo aceptaría? No está bien definido dónde está el límite entre el hardcoding y la informática real.abcab
. Todas menos las últimas 3 letras sonabcab
. Estos coinciden, y eliminar un número menor de letras no coincide.Respuestas:
C #, n = 128 en aproximadamente 2:40
Extender a n = 256 requeriría cambiar a
BigInteger
las máscaras, lo que probablemente mata el rendimiento demasiado para que n = 128 pase sin nuevas ideas, y mucho menos n = 256.En Linux, compila
mono-csc
y ejecuta conmono
.Explicación básica
No voy a hacer una disección línea por línea, solo una descripción general de los conceptos.
Como regla general, me complace repetir el orden de 2 50 elementos en un programa combinatorio de fuerza bruta. Para llegar a n = 128, por lo tanto, es necesario utilizar un enfoque que no analice todas las cadenas de bits. Entonces, en lugar de trabajar hacia adelante desde cadenas de bits hasta secuencias de períodos, trabajo hacia atrás: dada una secuencia de períodos, ¿hay una cadena de bits que se da cuenta? Para n = 2 x hay un límite superior fácil de 2 x (x + 1) / 2 secuencias de período (vs 2 2 x cadenas de bits).
Algunos de los argumentos usan el lema de periodicidad de cadena :
Wlog Asumiré que todas las cadenas de bits en consideración comienzan con
0
.Dada una secuencia de períodos donde es el período del prefijo de longitud 2 i ( siempre), hay algunas observaciones fáciles sobre posibles valores de :
[p1 p2 ... pk]
pi
p0 = 1
pk+1
pk+1 ≥ pk
dado que un período de una cadenaS
también es un período de cualquier prefijo deS
.pk+1 = pk
siempre es una extensión posible: simplemente repita la misma cadena primitiva para el doble de caracteres.2k < pk+1 ≤ 2k+1
Es siempre una posible extensión. Es suficiente mostrar esto porque una cadena aperiódica de longitud se puede extender a una cadena aperiódica de longitud agregando cualquier letra que no sea su primera letra.pk+1 = 2k+1
L
L+1
Tome una cadena
Sx
de longitud 2 k cuyo período es y considere la cadena de longitud 2 k + 1 . Claramente tiene un período de 2 k +1. Supongamos que su período es más pequeño.pk
SxyS
SxyS
q
Entonces, según la periodicidad, el lema también es un período de , y dado que el mayor divisor es menor o igual que sus argumentos y es el período más pequeño, debemos ser un factor propio de 2 k +1. Como su cociente no puede ser 2, tenemos .
2k+1 + q ≤ 2k+1+1 ≤ 2k+1 + gcd(2k+1, q)
gcd(2k+1, q)
SxyS
q
q
q ≤ (2k+1)/3
Ahora, dado que es un período , debe ser un período de . Pero el período de es . Tenemos dos casos:
q ≤ 2k
SxyS
Sx
Sx
pk
gcd(pk, q) = pk
, o equivalentemente se divide exactamente en .pk
q
pk + q > 2k + gcd(pk, q)
para que el lema de periodicidad no fuerce un período menor.Considere primero el segundo caso. , contradiciendo la definición de como el período de . Por lo tanto, nos vemos obligados a llegar a la conclusión de que es un factor de .
pk > 2k + gcd(pk, q) - q ≥ 2k+1 - q ≥ 2k+1 - (2k+1)/3 ≥ 2q
pk
Sx
pk
q
Pero dado que
q
es un período deSx
y es el período de , el prefijo de longitud es solo copias del prefijo de longitud , por lo que vemos que también es un período de .pk
Sx
q
q/pk
pk
pk
SxyS
Por lo tanto, el período de
SxyS
es o 2 k +1. ¡Pero tenemos dos opciones para ! Como máximo, una opción de dará período , por lo que al menos uno dará período 2 k +1. QEDpk
y
y
pk
El lema de periodicidad nos permite rechazar algunas de las extensiones posibles restantes.
Cualquier extensión que no haya pasado una prueba de aceptación rápida o de rechazo rápido debe probarse de manera constructiva.
La construcción de una cadena de bits dada una secuencia de períodos es esencialmente un problema de satisfacción, pero tiene mucha estructura. Existen restricciones de igualdad simples implícitas en cada período de prefijo, por lo que utilizo una estructura de datos de conjunto de unión para combinar bits en grupos independientes. Esto fue suficiente para abordar n = 64, pero para n = 128 fue necesario ir más allá. Empleo dos líneas de argumento útiles:
2k - pk
M
es y el prefijo de longitud tiene punto, entonces el prefijo de longitud debe terminar en . Esto es más poderoso precisamente en los casos que de otro modo tendrían grupos más independientes, lo cual es conveniente.01M-1
L > M
L
L
1M
M
es y el prefijo de longitud tiene período con y termina en entonces debe en extremo hecho en . Esto es más poderoso en el extremo opuesto, cuando la secuencia del período comienza con muchos.0M
L > M
L - d
d < M
0d
10d
Si no obtenemos una contradicción inmediata al obligar al clúster con el primer bit (se supone que es cero) a uno, entonces fuerza bruta (con algunas micro optimizaciones) sobre los posibles valores para los clústeres no forzados. Tenga en cuenta que el orden está en un número descendente de unidades porque si el bit
i
th es uno, entonces el período no puede seri
y queremos evitar períodos que sean más cortos que los que ya están aplicados por la agrupación. Bajar aumenta las posibilidades de encontrar una asignación válida antes de tiempo.fuente
Rust, 32, 10s
11s29sen mi laptopLlámalo con el tamaño de bits como argumento de línea de comando.
Técnicas inteligentes: represente cadenas de bits directamente como números, use bittwiddling para verificar los ciclos. Solo busque la primera mitad de las cadenas de bits, aquellas que comienzan con 0, porque la matriz de períodos de una cadena de bits y su inverso (0 intercambiados por 1) son idénticos. Si todas las posibilidades para la posición final ya han ocurrido, no la busco.
Cosas más inteligentes:
Para deduplicar cada bloque (cadenas donde la primera mitad de los bits son iguales) utilizo un vector de bits, que es mucho más rápido que un hashset, ya que las longitudes del ciclo final no necesitan hash.
Además, omito los primeros pasos de la verificación del ciclo, porque sé que el ciclo final no puede ser más corto que el penúltimo ciclo.
Después de muchos perfiles, ahora puedo decir que casi todo el tiempo se usa de manera productiva, por lo que creo que se necesitarán mejoras algorítmicas para mejorar desde aquí. También cambié a enteros de 32 bits para ahorrar un poco más de tiempo.
Poner
bit-vec = "0.4.4"
en su Cargo.tomlSi desea ejecutar esto, clone esto: github.com/isaacg1/cycle luego
Cargo build --release
para compilar, luegoCargo run --release 32
para ejecutar.fuente
time
le da 27 segundos de usuario en mi computadora portátil.Óxido , 16
Pruébalo en línea!
Compilacion:
rustc -O <name>.rs
La cadena se implementa como un vector Bool.
La
next
función itera a través de las combinaciones;El
find_period
toma una rebanada de Bool y devuelve el punto;El
compute_count_array
hace el trabajo para cada una "potencia de dos" subsecuencia de cada una de las combinaciones Bools.Teóricamente, no se espera un desbordamiento hasta que
2^n
exceda el valor máximo de u64, es decirn > 64
. Este límite podría ampliarse con una prueba costosa en s = [verdadero, verdadero, ..., verdadero].La mala noticia es: devuelve 317 para n = 16, pero no sé por qué. Tampoco sé si lo hará en diez minutos para n = 32, ya
Vec<bool>
que no está optimizado para este tipo de cálculo.EDITAR
He logrado eliminar el límite de 64 para
n
. Ahora, no se bloqueará hasta quen
sea mayor que el entero máximo usize.Encontré por qué el código anterior devolvió 317 para
n=32
. Estaba contando conjuntos de períodos y no conjuntos de períodos. Había tres matrices con los mismos elementos:Ahora funciona. Todavía es lento pero funciona.
fuente
C - 16
Falla en valores superiores a 16 cuz de desbordamiento.
No tengo idea de qué tan rápido se ejecuta esto porque estoy en un Chromebook ejecutándolo en repl.it.
Simplemente implementa la pregunta a medida que se lee, revisa todas las cadenas de bits, calcula las matrices de períodos y comprueba si ya se han contado.
Solo compílelo con gcc, etc.
fuente
16
continuación, cuando el código se cambió para que los dosmalloc
s eranmalloc(...int*))
y...**
respectivamente16
imprimenAnswer: 320
como se espera, sin embargo32
impresosAnswer: 0
(y con bastante rapidez).n = 8
pero después de que se imprime el resultado, lo que significa que la pila está dañada. Probablemente esté escribiendo en algún lugar más allá de los bloques de memoria asignados.Haskell
Compilar con
ghc -O2
. Pruébalo en línea!Se ejecuta en menos de 0.1 segundos en el hardware de mi computadora portátil de 6 años
n=16
.n=32
toma9992 minutos, así que estoy factor 9 o 10 de descuento. Intenté almacenar en caché los períodos en una tabla de búsqueda para no tener que volver a calcularlos una y otra vez, pero esto rápidamente se queda sin memoria en mi máquina de 4GB.fuente
Python 2 (PyPy), 16
fuente
osboxes@osboxes:~/python$ python ascii_user.py 16 None
[C ++], 32, 4 minutos
fuente