Demasiados peones en un tablero de ajedrez


10

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


2
¿Cuál es el formato de salida?
Pomo de la puerta

2
Para la prueba: ¿alguien tiene alguna idea de los números involucrados? Encontré 3 soluciones para 2x2 y 28 soluciones para 4x4
edc65

1
@ edc65, lo hago 3, 30, 410. He verificado los 3 y 30 por un método alternativo.
Peter Taylor

1
Hice que mi código generara los primeros: 3, 30, 410, 6148, 96120, 1526700. Aunque no tengo forma de verificarlo. ¿Alguien tiene lo mismo?
cmxu

1
Para aclarar la precedencia del operador, cuando dice 2n^2peones, ¿es eso (2n)^2o 2(n^2)?
Reto Koradi

Respuestas:


9

Java, n = 87 en mi máquina

El resultado para n = 87 es

62688341832480765224168252369740581641682638216282495398959252035334029997073369148728772291668336432168


import java.math.BigInteger;

public class NonattackingPawns {

    static BigInteger count(int n) {
        BigInteger[][] a0 = new BigInteger[n+1][n*n+1], a1 = new BigInteger[n+1][n*n+1], tm;

        for(int h = 0; h <= n; h++) a0[h][0] = h%2==0? BigInteger.ONE: BigInteger.ZERO;

        for(int c = 1; c <= 2*n; c++) {     
            int minp = 0;
            for(int h = 0; h <= n; h++) {
                java.util.Arrays.fill(a1[h], BigInteger.ZERO);
                if(h>0) minp += c >= 2*h-c%2 ? 2*h - c%2 : c;

                int maxp = Math.min(n*(c-1)+h, n*n);
                for(int p = minp; p <= maxp; p++) {
                    BigInteger sum = a0[h][p-h];

                    if(c%2==1 && h>0) 
                        sum = sum.add(a0[h-1][p-h]);
                    else if(c%2==0 && h<n) 
                        sum = sum.add(a0[h+1][p-h]);

                    a1[h][p] = sum;
                }
            }
            tm=a0; a0=a1; a1=tm;
        }
        BigInteger[] s = new BigInteger[n*n+1];
        for(int p = 0; p <= n*n; p++) {
            BigInteger sum = BigInteger.ZERO;
            for(int h = 0; h <= n; h++) sum = sum.add(a0[h][p]);
            s[p] = sum;

        }

        BigInteger ans = BigInteger.ZERO;
        for(int p = 0; p < n*n; p++) ans = ans.add(s[p].multiply(s[p]));
        return ans.shiftLeft(1).add(s[n*n].multiply(s[n*n]));
    }

    public static void main(String[] args) {
        for(int n = 0;; n++) {
            System.out.println(n + " " + count(n));
        }
    }

}

Actualmente, utiliza un esquema de programación dinámica que toma operaciones O (n ^ 4) para calcular las formas de colocar ppeones en los cuadrados de un color 0 <= 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:

línea de peón

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 .


¿Puedes compartir el número para n = 87? ¿O al menos el orden de magnitud? Tiene que ser un número muy grande ...
Reto Koradi

Estoy un poco confundido sobre lo que estás haciendo aquí, ¡pero es muy impresionante!
cmxu

Creo que obtengo la mayor parte de su explicación, excepto cuando dice "La línea también puede ir a dos filas imaginarias sobre la parte superior del tablero"
cmxu

@Changming, eso solo significa que no hay peones en esa columna.
fiesta

@feersum Veo que tiene más sentido, voy a ver si puedo trabajar a través de la lógica y ver si puedo encontrar alguna manera de implementarlo aún más rápido.
cmxu

5

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

2x2:    3
4x4:    30
6x6:    410
8x8:    6148
10x10:  96120

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

public class Chess {
    public static void main(String[] args){
        System.out.println(solve(1));
        System.out.println(solve(2));
        System.out.println(solve(3));
        System.out.println(solve(4));
        System.out.println(solve(5));
    }
    static int solve(int n){
        int m =2*n;
        int[][] b = new int[m][m];
        for(int i = 0; i < m; i++){
            for(int j = 0; j < m; j++){
                b[i][j]=0;
            }
        }
        return count(m,m*m,m*m/2,m*m/2,0,b);
    }
    static int count(int n,int sqLeft, int bLeft, int wLeft, int count, int[][] b){
        if(sqLeft == 0){
            /*for(int i = 0; i < n; i++){
                for(int j = 0; j < n; j++){
                    System.out.print(b[i][j]);
                }
                System.out.println();
            }
            System.out.println();*/
            return count+1;
        }
        int x=(sqLeft-1)%n;
        int y=(sqLeft-1)/n;
        if(wLeft==0){
            if(y!=0){
                if ((x==0?true:b[x-1][y-1]!=1)&&(x==n-1?true:b[x+1][y-1]!= 1)) {
                    b[x][y] = 2;
                    return count(n, sqLeft-1, bLeft-1, wLeft, count, b);
                } else {
                    return 0;
                }
            } else {
                b[x][y]=2;
                return count(n,sqLeft-1,bLeft-1,wLeft,count,b);
            }
        } else if(bLeft==0){
            if(y!=n-1){
                if((x==0?true:b[x-1][y+1]!=2)&&(x==n-1?true:b[x+1][y+1]!=2)){
                    b[x][y]=1;
                    return count(n,sqLeft-1,bLeft,wLeft-1,count,b);
                } else {
                    return 0;
                }
            } else {
                b[x][y]=1;
                return count(n,sqLeft-1,bLeft,wLeft-1,count,b);
            }
        } else{
            if(y==0){
                if((x==0?true:b[x-1][y+1]!=2)&&(x==n-1?true:b[x+1][y+1]!=2)){
                    int[][] c=new int[n][n];
                    for(int i = 0; i < n; i++){
                        System.arraycopy(b[i], 0, c[i], 0, n);
                    }
                    b[x][y]=2;
                    c[x][y]=1;
                    return count(n,sqLeft-1,bLeft,wLeft-1,count,c)+count(n,sqLeft-1,bLeft-1,wLeft,count,b);
                } else {
                    b[x][y]=2;
                    return count(n,sqLeft-1,bLeft-1,wLeft,count,b);
                }
            }else if(y==n-1){
                if((x==0?true:b[x-1][y-1]!=1)&&(x==n-1?true:b[x+1][y-1]!=1)){
                    int[][] c=new int[n][n];
                    for(int i = 0; i < n; i++){
                        System.arraycopy(b[i], 0, c[i], 0, n);
                    }
                    b[x][y]=2;
                    c[x][y]=1;
                    return count(n,sqLeft-1,bLeft,wLeft-1,count,c)+count(n,sqLeft-1,bLeft-1,wLeft,count,b);
                } else {
                    b[x][y]=1;
                    return count(n,sqLeft-1,bLeft,wLeft-1,count,b);
                }
            }else{
                if(((x==0?true:b[x-1][y-1]!=1)&&(x==n-1?true:b[x+1][y-1]!=1))&&((x==0?true:b[x-1][y+1]!=2)&&(x==n-1?true:b[x+1][y+1]!=2))){
                    int[][] c=new int[n][n];
                    for(int i = 0; i < n; i++){
                        System.arraycopy(b[i], 0, c[i], 0, n);
                    }
                    b[x][y]=2;
                    c[x][y]=1;
                    return count(n,sqLeft-1,bLeft,wLeft-1,count,c)+count(n,sqLeft-1,bLeft-1,wLeft,count,b);
                } else if ((x==0?true:b[x-1][y-1]!=1)&&(x==n-1?true:b[x+1][y-1]!=1)){
                    b[x][y]=2;
                    return count(n,sqLeft-1,bLeft-1,wLeft,count,b);
                } else if ((x==0?true:b[x-1][y+1]!=2)&&(x==n-1?true:b[x+1][y+1]!=2)){
                    b[x][y]=1;
                    return count(n,sqLeft-1,bLeft,wLeft-1,count,b);
                } else {
                    return 0;
                }
            }
        }
    }
}

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!)


Estoy de acuerdo hasta 6148, y todavía no he producido ningún valor más allá de eso.
Peter Taylor

@PeterTaylor Well Agnishom dice que debería ser 3, 28, 408, así que dudo que 6148 sea correcto. Me pregunto qué estamos haciendo mal los dos.
cmxu

Bastante más rápido que el mío. +1 incluso si no estoy de acuerdo con los resultados
edc65

¡Hola! Nunca dije que era 28, 408 La secuencia correcta es 3,30,410, ...
Agnishom Chattopadhyay

¿Dijiste que @ edc65 tenía los valores correctos y sus valores son 28, 408?
cmxu

4

C ++ con pthreads, n = 147 156

El ú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:

78581036888824823496962250906481423170934266912694416068935442570913159064317737026762661986430581489873651515605659228918524818470493215413475827287931751145438407410447474283840161044747428384016104474742838741600447428384016104074

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:

  • Dado que los peones en los cuadrados negros solo pueden atacar a los peones en otros cuadrados negros, y lo mismo es cierto para los cuadrados blancos, los cuadrados negros y blancos son independientes y se pueden procesar por separado. Y dado que son equivalentes, solo necesitamos procesar uno de los dos.
  • El problema se vuelve mucho más fácil cuando se procesa el tablero diagonal por diagonal.

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:

  • Número de peones negros en la fila (o en otras palabras, la posición dentro de la diagonal que separa los peones negros de los peones blancos).
  • Recuento total de peones negros utilizados. Necesitamos rastrear todo por conteo de peones porque solo necesitamos el mismo número de peones negros y peones blancos al final. Al procesar las diagonales, los recuentos pueden ser diferentes y, al final, dar lugar a soluciones válidas.

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:

  • Si bien puede haber hasta 2 * n ^ 2 peones negros en los cuadrados negros, solo calculo los números de configuración hasta n ^ 2 peones negros. Como hay una simetría entre los peones blancos y negros, el conteo de configuración para k y 2 * n ^ 2-k es el mismo.
  • El número de soluciones se calcula al final a partir de los recuentos de configuración en los cuadrados negros basados ​​en una simetría similar. El número total de soluciones (que necesitan tener 2 * n ^ 2 peones de cada color) es el número de configuraciones para k peones negros en un color de cuadrados multiplicado por el número de configuraciones para 2 * n ^ 2-k peones negros en el otro color de cuadrados, sumados sobre todo k.
  • Además de almacenar los conteos de configuración por posición diagonal y conteo de peones, también almaceno el rango de conteos de peones que tienen configuraciones válidas por posición. Esto permite reducir sustancialmente el rango del bucle interno. Sin esto, descubrí que se estaban agregando muchos ceros. Obtuve una mejora sustancial en el rendimiento de esto.
  • El algoritmo se paraleliza bastante bien, particularmente en tamaños grandes. Las diagonales tienen que ser procesos secuenciales, por lo que hay una barrera al final de cada diagonal. Pero las posiciones dentro de la diagonal se pueden procesar en paralelo.
  • La elaboración de perfiles muestra que el cuello de botella está claramente en agregar valores bigint. Jugué con algunas variaciones del código, pero no está muy optimizado. Sospecho que podría haber una mejora significativa del ensamblaje en línea para usar adiciones de 64 bits con carry.

Código del algoritmo principal. THREADScontrola el número de subprocesos utilizados, donde el número de núcleos de CPU debe ser un punto de partida razonable:

#ifndef THREADS
#define THREADS 2
#endif

#if THREADS > 1
#include <pthread.h>
#endif

#include <vector>
#include <iostream>
#include <sstream>

#include "BigUint.h"

typedef std::vector<BigUint> BigUintVec;
typedef std::vector<int> IntVec;

static int N;
static int NPawn;
static int NPos;

static BigUintVec PawnC[2];
static IntVec PawnMinC[2];
static IntVec PawnMaxC[2];

#if THREADS > 1
static pthread_mutex_t ThreadMutex;
static pthread_cond_t ThreadCond;
static int BarrierCount;
#endif

#if THREADS > 1
static void ThreadBarrier()
{
    pthread_mutex_lock(&ThreadMutex);

    --BarrierCount;
    if (BarrierCount)
    {
        pthread_cond_wait(&ThreadCond, &ThreadMutex);
    }
    else
    {
        pthread_cond_broadcast(&ThreadCond);
        BarrierCount = THREADS;
    }

    pthread_mutex_unlock(&ThreadMutex);
}
#endif

static void* countThread(void* pData)
{
    int* pThreadIdx = static_cast<int*>(pData);
    int threadIdx = *pThreadIdx;

    int prevDiagMin = N - 1;
    int prevDiagMax = N;

    for (int iDiag = 1; iDiag < 2 * N; ++iDiag)
    {
        BigUintVec& rSrcC = PawnC[1 - iDiag % 2];
        BigUintVec& rDstC = PawnC[iDiag % 2];

        IntVec& rSrcMinC = PawnMinC[1 - iDiag % 2];
        IntVec& rDstMinC = PawnMinC[iDiag % 2];

        IntVec& rSrcMaxC = PawnMaxC[1 - iDiag % 2];
        IntVec& rDstMaxC = PawnMaxC[iDiag % 2];

        int diagMin = prevDiagMin;
        int diagMax = prevDiagMax;;
        if (iDiag < N)
        {
            --diagMin;
            ++diagMax;
        }
        else if (iDiag > N)
        {
            ++diagMin;
            --diagMax;
        }

        int iLastPos = diagMax;
        if (prevDiagMax < diagMax)
        {
            iLastPos = prevDiagMax;
        }

        for (int iPos = diagMin + threadIdx; iPos <= iLastPos; iPos += THREADS)
        {
            int nAdd = iPos - diagMin;

            for (int iPawn = nAdd; iPawn < NPawn; ++iPawn)
            {
                rDstC[iPos * NPawn + iPawn] = 0;
            }

            rDstMinC[iPos] = NPawn;
            rDstMaxC[iPos] = -1;

            int iFirstPrevPos = iPos;
            if (!nAdd)
            {
                iFirstPrevPos = prevDiagMin;
            }

            for (int iPrevPos = iFirstPrevPos;
                 iPrevPos <= prevDiagMax; ++iPrevPos)
            {
                int iLastPawn = rSrcMaxC[iPrevPos];
                if (iLastPawn + nAdd >= NPawn)
                {
                    iLastPawn = NPawn - 1 - nAdd;
                }

                if (rSrcMinC[iPrevPos] > iLastPawn)
                {
                    continue;
                }

                if (rSrcMinC[iPrevPos] < rDstMinC[iPos])
                {
                    rDstMinC[iPos] = rSrcMinC[iPrevPos];
                }

                if (iLastPawn > rDstMaxC[iPos])
                {
                    rDstMaxC[iPos] = iLastPawn;
                }

                for (int iPawn = rSrcMinC[iPrevPos];
                     iPawn <= iLastPawn; ++iPawn)
                {
                    rDstC[iPos * NPawn + iPawn + nAdd] += rSrcC[iPrevPos * NPawn + iPawn];
                }
            }

            if (rDstMinC[iPos] <= rDstMaxC[iPos])
            {
                rDstMinC[iPos] += nAdd;
                rDstMaxC[iPos] += nAdd;
            }
        }

        if (threadIdx == THREADS - 1 && diagMax > prevDiagMax)
        {
            int pawnFull = (iDiag + 1) * (iDiag + 1);
            rDstC[diagMax * NPawn + pawnFull] = 1;
            rDstMinC[diagMax] = pawnFull;
            rDstMaxC[diagMax] = pawnFull;
        }

        prevDiagMin = diagMin;
        prevDiagMax = diagMax;

#if THREADS > 1
        ThreadBarrier();
#endif
    }

    return 0;
}

static void countPawns(BigUint& rRes)
{
    NPawn = N * N + 1;
    NPos = 2 * N;

    PawnC[0].resize(NPos * NPawn);
    PawnC[1].resize(NPos * NPawn);

    PawnMinC[0].assign(NPos, NPawn);
    PawnMinC[1].assign(NPos, NPawn);

    PawnMaxC[0].assign(NPos, -1);
    PawnMaxC[1].assign(NPos, -1);

    PawnC[0][(N - 1) * NPawn + 0] = 1;
    PawnMinC[0][N - 1] = 0;
    PawnMaxC[0][N - 1] = 0;

    PawnC[0][N * NPawn + 1] = 1;
    PawnMinC[0][N] = 1;
    PawnMaxC[0][N] = 1;

#if THREADS > 1
    pthread_mutex_init(&ThreadMutex, 0);
    pthread_cond_init(&ThreadCond, 0);

    BarrierCount = THREADS;

    int threadIdxA[THREADS] = {0};
    pthread_t threadA[THREADS] = {0};
    for (int iThread = 0; iThread < THREADS; ++iThread)
    {
        threadIdxA[iThread] = iThread;
        pthread_create(threadA + iThread, 0, countThread, threadIdxA + iThread);
    }

    for (int iThread = 0; iThread < THREADS; ++iThread)
    {
        pthread_join(threadA[iThread], 0);
    }

    pthread_cond_destroy(&ThreadCond);
    pthread_mutex_destroy(&ThreadMutex);
#else
    int threadIdx = 0;
    countThread(&threadIdx);
#endif

    BigUint solCount;
    BigUintVec& rResC = PawnC[1];
    for (int iPawn = 0; iPawn < NPawn; ++iPawn)
    {
        BigUint nComb = rResC[(N - 1) * NPawn + iPawn];

        nComb *= nComb;
        if (iPawn < NPawn - 1)
        {
            nComb *= 2;
        }

        solCount += nComb;
    }

    std::string solStr;
    solCount.toDecString(solStr);
    std::cout << solStr << std::endl;
}

int main(int argc, char* argv[])
{
    std::istringstream strm(argv[1]);
    strm >> N;

    BigUint res;
    countPawns(res);

    return 0;
}

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:

#ifndef BIG_UINT_H
#define BIG_UINT_H

#include <cstdint>
#include <string>
#include <vector>

class BigUint
{
public:
    BigUint()
      : m_size(1),
        m_cap(MIN_CAP),
        m_valA(m_fixedValA)
    {
        m_valA[0] = 0;
    }

    BigUint(uint32_t val)
      : m_size(1),
        m_cap(MIN_CAP),
        m_valA(m_fixedValA)
    {
        m_valA[0] = val;
    }

    BigUint(const BigUint& rhs)
      : m_size(rhs.m_size),
        m_cap(MIN_CAP),
        m_valA(m_fixedValA)
    {
        if (m_size > MIN_CAP)
        {
            m_cap = m_size;
            m_valA = new uint32_t[m_cap];
        }

        for (int iVal = 0; iVal < m_size; ++iVal)
        {
            m_valA[iVal] = rhs.m_valA[iVal];
        }
    }

    ~BigUint()
    {
        if (m_cap > MIN_CAP)
        {
            delete[] m_valA;
        }
    }

    BigUint& operator=(uint32_t val)
    {
        m_size = 1;
        m_valA[0] = val;

        return *this;
    }

    BigUint& operator=(const BigUint& rhs)
    {
        if (rhs.m_size > m_cap)
        {
            if (m_cap > MIN_CAP)
            {
                delete[] m_valA;
            }

            m_cap = rhs.m_size;
            m_valA = new uint32_t[m_cap];
        }

        m_size = rhs.m_size;

        for (int iVal = 0; iVal < m_size; ++iVal)
        {
            m_valA[iVal] = rhs.m_valA[iVal];
        }

        return *this;
    }

    BigUint& operator+=(const BigUint& rhs)
    {
        if (rhs.m_size > m_size)
        {
            resize(rhs.m_size);
        }

        uint64_t sum = 0;
        for (int iVal = 0; iVal < m_size; ++iVal)
        {
            sum += m_valA[iVal];
            if (iVal < rhs.m_size)
            {
                sum += rhs.m_valA[iVal];
            }
            m_valA[iVal] = sum;
            sum >>= 32u;
        }

        if (sum)
        {
            resize(m_size + 1);
            m_valA[m_size - 1] = sum;
        }

        return *this;
    }

    BigUint& operator*=(const BigUint& rhs)
    {
        int resSize = m_size + rhs.m_size - 1;
        uint32_t* resValA = new uint32_t[resSize];

        uint64_t sum = 0;

        for (int iResVal = 0; iResVal < resSize; ++iResVal)
        {
            uint64_t carry = 0;

            for (int iLhsVal = 0;
                 iLhsVal <= iResVal && iLhsVal < m_size; ++iLhsVal)
            {
                int iRhsVal = iResVal - iLhsVal;
                if (iRhsVal < rhs.m_size)
                {
                    uint64_t prod = m_valA[iLhsVal];
                    prod *= rhs.m_valA[iRhsVal];
                    uint64_t newSum = sum + prod;
                    if (newSum < sum)
                    {
                        ++carry;
                    }
                    sum = newSum;
                }
            }

            resValA[iResVal] = sum & UINT64_C(0xFFFFFFFF);
            sum >>= 32u;
            sum += carry << 32u;
        }

        if (resSize > m_cap)
        {
            if (m_cap > MIN_CAP)
            {
                delete[] m_valA;
            }

            m_cap = resSize;
            m_valA = resValA;
        }
        else
        {
            for (int iVal = 0; iVal < resSize; ++iVal)
            {
                m_valA[iVal] = resValA[iVal];
            }

            delete[] resValA;
        }

        m_size = resSize;

        if (sum)
        {
            resize(m_size + 1);
            m_valA[m_size - 1] = sum;
        }

        return *this;
    }

    void divMod(uint32_t rhs, uint32_t& rMod)
    {
        uint64_t div = 0;
        for (int iVal = m_size - 1; iVal >= 0; --iVal)
        {
            div <<= 32u;
            div += m_valA[iVal];

            uint64_t val = div / rhs;
            div -= val * rhs;

            if (val || iVal == 0 || iVal < m_size - 1)
            {
                m_valA[iVal] = val;
            }
            else
            {
                --m_size;
            }
        }

        rMod = div;
    }

    void toDecString(std::string& rStr) const
    {
        std::vector<char> digits;

        BigUint rem(*this);
        while (rem.m_size > 1 || rem.m_valA[0])
        {
            uint32_t digit = 0;
            rem.divMod(10, digit);
            digits.push_back(digit);
        }

        if (digits.empty())
        {
            rStr = "0";
        }
        else
        {
            rStr.clear();
            rStr.reserve(digits.size());

            for (int iDigit = digits.size() - 1; iDigit >= 0; --iDigit)
            {
                rStr.append(1, '0' + digits[iDigit]);
            }
        }
    }

private:
    static const int MIN_CAP = 8;

    void resize(int newSize)
    {
        if (newSize > m_cap)
        {
            uint32_t* newValA = new uint32_t[newSize];

            for (int iVal = 0; iVal < m_size; ++iVal)
            {
                newValA[iVal] = m_valA[iVal];
            }

            if (m_cap > MIN_CAP)
            {
                delete[] m_valA;
            }

            m_cap = newSize;
            m_valA = newValA;
        }

        for (int iVal = m_size; iVal < newSize; ++iVal)
        {
            m_valA[iVal] = 0;
        }

        m_size = newSize;
    }

    int m_size;
    int m_cap;

    uint32_t* m_valA;
    uint32_t m_fixedValA[MIN_CAP];
};

#endif // BIG_UINT_H

0

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

class main
{
  public  Void main(){

    echo(calculate(1))
    echo(calculate(2))
    echo(calculate(3))
    echo(calculate(4))
    echo(calculate(5))

  }

  public static  Int calculate(Int n){

    n *= 2
    //Initialize the array -  Definitely a weakpoint, but only runs once
    Bool[][] white := [,]
    n.times{ 
      row := [,]
      n.times{ row.add(false) }
      white.add(row)
    }

    return recurse(white, -1, 0, n, n*n/2)
  }

  private static  Int recurse(Bool[][] white, Int lastPlacement, Int numWhites, Int n, Int totalWhite){
    if(totalWhite - numWhites > n*n - 1 - lastPlacement) return 0
    lastPlacement++
    Int row := lastPlacement / n
    Int col := lastPlacement % n
    if(white[row][col]){ return recurse(white, lastPlacement, numWhites, n, totalWhite)}
    Bool[][] whiteCopy := copy(white)
    whiteCopy[row][col] = true
    Int result := fillIn(whiteCopy, numWhites + 1, totalWhite)
    if(result == -1){
      return recurse(white, lastPlacement, numWhites,n, totalWhite);
    }
    else if(result == totalWhite){
      //echo("Found solution")
      //echo("WhiteCopy = $whiteCopy")
      return recurse(white, lastPlacement, numWhites,n, totalWhite) + 1;
    }
    else return recurse(whiteCopy, lastPlacement, result,n, totalWhite) + recurse(white, lastPlacement, numWhites,n, totalWhite)


  }

  //Every white must be attacking other whites, so fill in the grid with all necessary points
  //Stop if number of whites used goes too high
  private static Int fillIn(Bool[][] white, Int count, Int n){
    white[0..-2].eachWhile |Bool[] row, Int rowIndex -> Bool?| {
      return row.eachWhile |Bool isWhite, Int colIndex -> Bool?|{
        if(isWhite){
          //Catching index out of bounds is faster than checking index every time
          try{
            if(colIndex > 0 && !white[rowIndex + 1][colIndex - 1]){
              white[rowIndex + 1][colIndex - 1] = true
              count++
            }
            if(!white[rowIndex + 1][colIndex + 1]){
              white[rowIndex + 1][colIndex + 1] = true
              count++
            }
          } catch {}
        }
        if(count > n){ count = -1; return true}
        return null
      }//End row.each
    }//End white.each
    return count
  }

  private static Bool[][] copy(Bool[][] orig){
    Bool[][] copy := [,]
    orig.each{
      copy.add(it.dup)
    }
    return copy
  }

}

Salida

Solo llega a 5 en este momento, pero creo que la mayor parte del problema está en la implementación.

3
30
410
6148
96120

Prueba


Esa es mi estrategia también, pero parece demasiado lenta en comparación con las otras soluciones publicadas aquí.
edc65

@ edc65 Los enfoques que enumeran las soluciones no tendrán oportunidad. Si hubiera alguna duda, el gran número producido por el algoritmo de feersum es prueba de ello. Aquí se debe utilizar algún tipo de algoritmo de programación dinámica que calcule la cantidad de soluciones sin enumerarlas.
Reto Koradi
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.