Dado um número inteiro 2n, encontre o número possível de maneiras pelas quais 2n ^ 2 peões pretos e 2n ^ 2 peões brancos podem ser organizados em um tabuleiro de xadrez 2n por 2n, de modo que nenhum peão ataque outro.
- Um peão preto pode atacar apenas um peão branco e vice-versa.
- Seguem as regras usuais de ataque de xadrez, ou seja, peões brancos atacam quadrados imediatamente na diagonal na frente e os peões pretos atacam quadrados imediatamente na diagonal para trás (como visto pelo observador branco).
- Toda rotação, reflexões contam como distintas.
O programa que pode gerar todas as configurações possíveis para o valor mais alto de 2n em 120 segundos vence. (Todos os programas são bem-vindos, no entanto)
Por exemplo, o programa de Alice pode lidar com até n = 16 em 120 segundos, enquanto o de Bob pode lidar com até n = 20 no mesmo tempo. Bob vence.
Plataforma: Linux 2.7GHz @ 4 CPUs
fastest-code
combinatorics
chess
Agnishom Chattopadhyay
fonte
fonte
2n^2
peões, é isso(2n)^2
ou2(n^2)
?Respostas:
Java, n = 87 na minha máquina
O resultado para n = 87 é
Atualmente, ele usa um esquema de programação dinâmica que executa operações O (n ^ 4) para calcular as maneiras de colocar
p
peões nos quadrados de uma cor0 <= p <= n^2
. Eu acho que deveria ser possível fazer isso com muito mais eficiência.Confira os resultados aqui.
Explicação
Em uma solução válida, os peões brancos mais baixos em cada coluna devem formar uma linha em zigue-zague assim:
Ou seja, a altura da linha na coluna c deve ser +/- 1 em relação à sua posição na coluna c - 1 . A linha também pode ir para duas linhas imaginárias acima da parte superior do quadro.
Podemos usar programação dinâmica para encontrar o número de maneiras para desenhar uma linha sobre os primeiros c colunas que inclui p peões sobre as colunas, é a altura h na c th coluna, utilizando os resultados para a coluna c - 1 , alturas h + / - 1 e número de peões p - h .
fonte
Java
Atualmente, meu código é muito longo e tedioso, estou trabalhando para torná-lo mais rápido. Eu uso um método recursivo para encontrar os valores. Ele calcula os primeiros 5 em 2 ou 3 segundos, mas fica muito mais lento depois. Além disso, ainda não tenho certeza se os números estão corretos, mas os primeiros parecem se alinhar com os comentários. Todas as sugestões são bem-vindas.
Resultado
Explicação
A idéia básica é recursão. Basicamente, você começa com um tabuleiro vazio, um tabuleiro com todos os zeros. O método recursivo apenas verifica se é possível colocar um peão preto ou branco na próxima posição; se pode colocar apenas uma cor, coloca-o lá e chama a si mesmo. Se ele pode colocar as duas cores, chama a si mesmo duas vezes, uma com cada cor. Cada vez que se chama, diminui os quadrados à esquerda e a cor apropriada à esquerda. Quando preenche todo o tabuleiro, retorna a contagem atual + 1. Se descobrir que não há como colocar um peão preto ou branco na próxima posição, ele retornará 0, o que significa que é um caminho morto.
Código
Experimente aqui (não roda rápido o suficiente para o Ideone, para que o último valor não seja impresso, parece que minha solução não é muito boa!)
fonte
C ++ com pthreads, n =
147156O resultado mais recente é a execução do mesmo código de antes em uma máquina mais robusta. Agora, ele era executado em um desktop com um i7 quad-core (Core i7-4770), que chegava a n = 156 em 120 segundos. O resultado é:
Isso está usando um algoritmo de programação dinâmica. Inicialmente, considerei abordagens nas quais o resultado seria construído linha por linha, mas nunca consegui encontrar uma maneira de expandir a solução sem rastrear uma tonelada de estado.
Os principais insights que permitiram uma solução razoavelmente eficiente foram:
Se você observar uma diagonal de uma configuração válida, ela sempre consistirá em uma sequência de peões pretos, seguida por uma sequência de peões brancos (onde qualquer sequência também pode estar vazia). Em outras palavras, cada diagonal pode ser totalmente caracterizada pelo número de peões pretos.
Portanto, o estado rastreado para cada diagonal é o número de configurações de peões válidas para cada combinação de:
Ao passar de uma diagonal para a próxima, há outra restrição para criar soluções válidas: A posição que separa os peões pretos dos peões brancos não pode aumentar. Portanto, o número de configurações válidas é calculado como a soma das configurações válidas da diagonal anterior para posições iguais ou maiores.
A etapa básica do DP é então muito simples. Cada valor em uma diagonal é apenas uma soma dos valores da diagonal anterior. A única parte um pouco dolorosa é calcular os índices e as faixas de loop corretamente. Como estamos trabalhando nas diagonais, o comprimento aumenta durante a primeira metade do cálculo e diminui na segunda metade, o que torna o cálculo do intervalo do loop mais complicado. Há também algumas considerações para os valores no limite do quadro, pois eles só têm vizinhos diagonais de um lado ao passar de diagonal para diagonal.
A quantidade de memória usada é O (n ^ 3). Eu mantenho duas cópias dos dados do estado e pingue-pongue entre eles. Eu acredito que seria possível operar com uma única instância dos dados do estado. Mas você deve ter muito cuidado para que nenhum valor seja atualizado antes que os valores antigos sejam totalmente consumidos. Além disso, não funcionaria bem para o processamento paralelo que apresentei.
A complexidade do tempo de execução é ... polinomial. Existem 4 loops aninhados no algoritmo, portanto, à primeira vista, ele se pareceria com O (n ^ 4). Mas você obviamente precisa de bigints nesses tamanhos, e os próprios números também ficam mais longos em tamanhos maiores. O número de dígitos no resultado parece aumentar proporcionalmente a n, o que tornaria a coisa toda O (n ^ 5). Por outro lado, encontrei algumas melhorias de desempenho, o que evita passar por toda a gama de todos os loops.
Portanto, embora esse algoritmo ainda seja bastante caro, ele vai muito além dos algoritmos que enumeram soluções, todas exponenciais.
Algumas notas sobre a implementação:
Código do algoritmo principal.
THREADS
controla o número de threads usados, em que o número de núcleos da CPU deve ser um ponto de partida razoável:Isso também precisa de uma grande aula que escrevi para esse fim. Observe que essa não é uma classe bigint de uso geral. Ele faz o suficiente para suportar as operações usadas por este algoritmo específico:
fonte
Fantom
Aqui está um post inicial que configura a estrutura. Eu acho que o procedimento é relativamente bom, mas a implementação no momento é meio ruim. Preciso provavelmente tentar minimizar o número de cálculos que estou fazendo e, em vez disso, passar mais constantes.
Estratégia
Basicamente, cada peão branco deve estar atacando outros peões brancos. Então eu começo colocando um peão branco, colocando peões em cada lugar que ele ataca, e essencialmente preenchendo o tabuleiro com todos os lugares que um peão branco tem que ir. Paro se já adicionei muitos peões brancos. Se, no final, tiver exatamente 2n ^ 2 peões, é uma solução. Se for menor do que isso, adicione outro peão branco em algum lugar, preencha todos os lugares necessários e conte novamente. Eu divido recursivamente toda vez que um preenchimento com menos de 2n ^ 2 é encontrado e calculo o número de soluções com e sem o último peão que adicionei.
Código
Resultado
Agora chega a 5 agora, mas acho que a maior parte do problema está em implementação.
Teste
fonte