Dado un número entero 2n, encuentre el número de formas posibles en que 2n ^ 2 peones negros y 2n ^ 2 peones blancos se pueden organizar en un tablero de ajedrez 2n por 2n de modo que ningún peón ataque a otro.
- Un peón negro solo puede atacar a un peón blanco, y viceversa.
- Siguen las reglas habituales de ajedrez para atacar, es decir, los peones blancos atacan cuadrados inmediatamente en diagonal al frente, y los peones negros atacan cuadrados inmediatamente en diagonal hacia atrás (como lo ve el observador blanco).
- Toda la rotación, los reflejos cuentan como distintos.
El programa que puede generar todas las configuraciones posibles para el valor más alto de 2n en 120 segundos gana. (Sin embargo, todos los programas son bienvenidos)
Por ejemplo, el programa de Alice puede manejar hasta n = 16 en 120 segundos, mientras que el de Bob puede manejar hasta n = 20 en el mismo tiempo. Bob gana.
Plataforma: Linux 2.7GHz @ 4 CPU
fastest-code
combinatorics
chess
Agnishom Chattopadhyay
fuente
fuente
2n^2
peones, ¿es eso(2n)^2
o2(n^2)
?Respuestas:
Java, n = 87 en mi máquina
El resultado para n = 87 es
Actualmente, utiliza un esquema de programación dinámica que toma operaciones O (n ^ 4) para calcular las formas de colocar
p
peones en los cuadrados de un color0 <= p <= n^2
. Creo que debería ser posible hacerlo mucho más eficientemente.Mira los resultados aquí.
Explicación
En una solución válida, los peones blancos más bajos en cada columna deben formar una línea en zigzag como esta:
Es decir, la altura de la línea en la columna c debe ser +/- 1 desde su posición en la columna c - 1 . La línea también puede ir a dos filas imaginarias sobre la parte superior del tablero.
Podemos usar la programación dinámica para encontrar la cantidad de formas de dibujar una línea en las primeras columnas c que incluye p peones en esas columnas, está en altura h en la columna c , usando los resultados para la columna c - 1 , alturas h + / - 1 , y número de peones p - h .
fuente
Java
Actualmente, mi código es muy largo y tedioso, estoy trabajando para hacerlo más rápido. Yo uso un método recursivo para encontrar los valores. Calcula los primeros 5 en 2 o 3 segundos, pero luego se vuelve mucho más lento. Además, todavía no estoy seguro de si los números son correctos, pero los primeros parecen alinearse con los comentarios. Cualquier sugerencia es bienvenida.
Salida
Explicación
La idea básica es la recursividad. Básicamente comienzas con un tablero vacío, un tablero con todos los ceros. El método recursivo solo verifica si puede poner un peón negro o blanco en la siguiente posición, si solo puede poner un color, lo pone allí y se llama a sí mismo. Si puede poner ambos colores, se llama a sí mismo dos veces, uno con cada color. Cada vez que se llama a sí mismo, disminuye los cuadrados que quedan y queda el color apropiado. Cuando ha llenado todo el tablero, devuelve el conteo actual + 1. Si descubre que no hay forma de poner un peón negro o blanco en la siguiente posición, devuelve 0, lo que significa que es un camino muerto.
Código
Pruébelo aquí (no funciona lo suficientemente rápido para Ideone, por lo que el último valor no se imprime, ¡parece que mi solución no es muy buena!)
fuente
C ++ con pthreads, n =
147156El último resultado es ejecutar el mismo código que antes en una máquina más robusta. Esto ahora se ejecutó en un escritorio con un i7 de cuatro núcleos (Core i7-4770), que llegó a n = 156 en 120 segundos. El resultado es:
Esto está utilizando un algoritmo de programación dinámico. Inicialmente reflexioné acerca de los enfoques donde el resultado se construiría fila por fila, pero nunca pude encontrar una manera de expandir la solución sin rastrear una tonelada de estado.
Las ideas clave que permitieron una solución razonablemente eficiente fueron:
Si observa una diagonal de una configuración válida, siempre consiste en una secuencia de peones negros seguida de una secuencia de peones blancos (donde cualquiera de las secuencias también puede estar vacía). En otras palabras, cada diagonal se puede caracterizar completamente por su número de peones negros.
Por lo tanto, el estado seguido para cada diagonal es el número de configuraciones válidas de peones para cada combinación de:
Al pasar de una diagonal a la siguiente, hay otra restricción para construir soluciones válidas: la posición que separa los peones negros de los blancos no puede aumentar. Por lo tanto, el número de configuraciones válidas se calcula como la suma de las configuraciones válidas de la diagonal anterior para posiciones iguales o mayores.
El paso básico de DP es entonces muy simple. Cada valor en una diagonal es solo una suma de valores de la diagonal anterior. La única parte algo dolorosa es calcular los índices y los rangos de bucle correctamente. Como estamos trabajando en diagonales, la longitud aumenta durante la primera mitad del cálculo y disminuye durante la segunda mitad, lo que hace que el cálculo de los rangos de bucle sea más engorroso. También hay algunas consideraciones para los valores en el límite del tablero, ya que solo tienen vecinos diagonales en un lado al pasar de diagonal a diagonal.
La cantidad de memoria utilizada es O (n ^ 3). Guardo dos copias de los datos del estado y ping pong entre ellos. Creo que sería posible operar con una sola instancia de los datos del estado. Pero debe tener mucho cuidado de que no se actualicen los valores antes de que los valores antiguos se consuman por completo. Además, no funcionaría bien para el procesamiento paralelo que presenté.
La complejidad del tiempo de ejecución es ... polinomial. Hay 4 bucles anidados en el algoritmo, por lo que a primera vista se vería como O (n ^ 4). Pero obviamente necesita bigints en estos tamaños, y los números mismos también se alargan en tamaños más grandes. El número de dígitos en el resultado parece aumentar aproximadamente proporcionalmente a n, lo que haría que todo O (n ^ 5). Por otro lado, encontré algunas mejoras de rendimiento, lo que evita pasar por la gama completa de todos los bucles.
Entonces, si bien este es un algoritmo bastante costoso, va mucho más lejos que los algoritmos que enumeran soluciones, que son todas exponenciales.
Algunas notas sobre la implementación:
Código del algoritmo principal.
THREADS
controla el número de subprocesos utilizados, donde el número de núcleos de CPU debe ser un punto de partida razonable:Esto también necesita una clase bigint que escribí para este propósito. Tenga en cuenta que esta no es una clase bigint de propósito general. Hace lo suficiente para admitir las operaciones utilizadas por este algoritmo específico:
fuente
Fantom
Aquí hay una publicación inicial que configura el marco. Creo que el procedimiento es relativamente bueno, pero la implementación en este momento apesta. Probablemente necesito tratar de minimizar la cantidad de cálculos que estoy haciendo y, en su lugar, simplemente pasar más constantes.
Estrategia
Básicamente, cada peón blanco debe estar atacando a otros peones blancos. Así que empiezo colocando un peón blanco, colocando peones en cada lugar donde ataca, y esencialmente completando el tablero con todos los lugares a los que TIENE que ir un peón blanco. Me detengo si ya he agregado demasiados peones blancos. Si, al final de esto, tengo exactamente 2n ^ 2 peones, es una solución. Si es menos que eso, agrega otro peón blanco en alguna parte, llena todos los lugares requeridos y cuenta nuevamente. Me divido recursivamente cada vez que se encuentra un relleno con menos de 2n ^ 2, y calculo el número de soluciones con y sin el último peón que agregué.
Código
Salida
Solo llega a 5 en este momento, pero creo que la mayor parte del problema está en la implementación.
Prueba
fuente