Resolvendo palavras cruzadas em cores japonesas na velocidade da luz

Palavras cruzadas em japonĂȘs (tambĂ©m nonograms) sĂŁo quebra-cabeças lĂłgicos nos quais uma imagem de pixel Ă© criptografada. VocĂȘ precisa resolver as palavras cruzadas usando nĂșmeros localizados Ă  esquerda das linhas e acima das colunas.


O tamanho das palavras cruzadas pode atingir até 150 x 150. O jogador usa lógica especial para calcular a cor de cada célula. A solução pode levar alguns minutos em palavras cruzadas para iniciantes e dezenas de horas em quebra-cabeças complexos.


Um bom algoritmo pode resolver o problema muito mais rapidamente. O texto descreve como escrever uma solução que funciona quase instantaneamente usando os algoritmos mais adequados (que geralmente levam a uma solução), bem como suas otimizaçÔes e o uso de recursos do C ++ (que reduzem o tempo de execução por vårias dezenas de vezes).



Na versĂŁo mĂłvel do Habr, as fĂłrmulas no texto podem nĂŁo ser exibidas (nĂŁo em todos os navegadores mĂłveis) - use a versĂŁo completa ou outro navegador mĂłvel, se detectar problemas com as fĂłrmulas


Regras do jogo


Inicialmente, a tela de palavras cruzadas Ă© branca. Para palavras cruzadas em preto e branco, vocĂȘ precisa restaurar o local das cĂ©lulas negras.


Nas palavras cruzadas em preto e branco, o nĂșmero de nĂșmeros de cada linha (Ă  esquerda da tela) ou de cada coluna (parte superior da tela) mostra quantos grupos de cĂ©lulas pretas estĂŁo na linha ou coluna correspondente e os prĂłprios nĂșmeros mostram quantas cĂ©lulas mescladas cada um desses grupos contĂ©m. Conjunto de nĂșmeros [ 3 , 2 , 5 ] significa que em uma determinada linha existem trĂȘs grupos consecutivos de 3 , 2 e 5 cĂ©lulas pretas seguidas. Os grupos podem ser organizados em uma fileira aleatoriamente, sem violar a ordem relativa (os nĂșmeros especificam seu comprimento e a posição deve ser adivinhada), mas devem ser separados por pelo menos uma cĂ©lula branca.



Exemplo correto


Nas palavras cruzadas em cores, cada grupo ainda tem sua própria cor - qualquer, exceto o branco, é uma cor de fundo. Grupos vizinhos de cores diferentes podem ficar lado a lado, mas para grupos vizinhos das mesmas cores, a separação por pelo menos uma célula branca ainda é necessåria.


O que nĂŁo Ă© uma palavra cruzada japonesa


Cada imagem de pixel pode ser criptografada como um jogo de palavras cruzadas. Mas pode não ser possível restaurå-lo de volta - o quebra-cabeça resultante pode ter mais de uma solução ou uma solução, mas não pode ser resolvido logicamente. Em seguida, as células restantes no jogo devem ser adivinhadas usando a tecnologia xamùnica usando computadores quùnticos .


Tais palavras cruzadas nĂŁo sĂŁo palavras cruzadas, mas graphomania. Acredita-se que as palavras cruzadas corretas sejam tais que, de maneira lĂłgica, vocĂȘ possa encontrar uma solução Ășnica.


O "caminho lĂłgico" Ă© a capacidade de restaurar cada cĂ©lula uma por uma, considerando a linha / coluna separadamente ou sua interseção. Se isso nĂŁo for possĂ­vel, o nĂșmero de opçÔes de resposta consideradas pode ser muito, muito mais do que uma pessoa pode calcular por si mesma.



O nonograma errado Ă© a Ășnica solução, mas Ă© impossĂ­vel resolver normalmente. CĂ©lulas "insolĂșveis" rotuladas em laranja. Retirado da Wikipedia.


Essa restrição é explicada da seguinte maneira - no caso mais geral, as palavras cruzadas japonesas são um problema de NP-completo. No entanto, adivinhar não se torna uma tarefa completa de NP, se houver um algoritmo que, a cada instante, indique inequivocamente quais células abrir mais. Todas as palavras cruzadas usadas por seres humanos (exceto pelo método Monte Carlo tentativa e erro) são baseados nisso.


A maioria das palavras cruzadas ortodoxas divide-se por 5 larguras e alturas, nĂŁo hĂĄ linhas que possam ser contadas instantaneamente (aquelas em que cĂ©lulas coloridas obstruem todos os lugares ou elas nĂŁo existem) e o nĂșmero de cores complementares Ă© limitado. Esses requisitos nĂŁo sĂŁo necessĂĄrios.


As palavras cruzadas erradas mais simples:


|1 1| --+---+ 1| | 1| | --+---+ 

FreqĂŒentemente, a arte pixel codificada, que usa um padrĂŁo de "tabuleiro de damas" para simular um gradiente, nĂŁo Ă© resolvida ao contrĂĄrio. É melhor entender se vocĂȘ digitar "gradiente de pixelart" na pesquisa. O gradiente Ă© como estas palavras cruzadas 2x2 fayl.



Possíveis soluçÔes


Temos o tamanho das palavras cruzadas, uma descrição das cores e todas as linhas e colunas. Como montar uma imagem a partir disso:


Pesquisa completa


Para cada cĂ©lula, classificamos todos os estados possĂ­veis e verificamos se Ă© satisfatĂłrio. A complexidade dessa solução para palavras cruzadas em preto e branco O ( N ∗ M ∗ 2 N ∗ M ) para cor O ( N * M * c o r e s N * H ) . Por analogia com a classificação de palhaços, essa solução tambĂ©m pode ser chamada de palhaço. É adequado para o tamanho 5x5.


Backtracking


Todos os mĂ©todos possĂ­veis de organizar grupos horizontais de cĂ©lulas sĂŁo resolvidos. Colocamos um grupo de cĂ©lulas em uma linha, verifique se ele nĂŁo quebra a descrição dos grupos de colunas. Se quebrar, colocamos mais uma cĂ©lula, verificamos novamente. Set - vĂĄ para o prĂłximo grupo. Se vocĂȘ nĂŁo pode colocar um grupo de forma alguma, revertemos dois grupos, reorganizamos o grupo anterior e assim por diante, atĂ© colocarmos o Ășltimo grupo com ĂȘxito.


Separadamente para cada linha


Esta decisão é muito melhor e é verdadeiramente verdadeira. Podemos analisar cada linha e cada coluna individualmente. Cada linha tentarå revelar o måximo de informaçÔes.


O algoritmo de cada cĂ©lula da linha obtĂ©m mais informaçÔes - pode ser impossĂ­vel colorir a cĂ©lula com alguma cor, os grupos nĂŁo convergirĂŁo. VocĂȘ nĂŁo pode expandir completamente a linha imediatamente, mas as informaçÔes recebidas "ajudarĂŁo" a se abrir melhor para vĂĄrias colunas e, quando começarmos a analisĂĄ-las, elas "ajudarĂŁo" novamente as linhas, e assim por diante por vĂĄrias iteraçÔes, atĂ© que todas as cĂ©lulas tenham uma cor possĂ­vel.


DecisĂŁo verdadeiramente verdadeira


Uma linha, duas cores


A adivinhação eficaz da “linha Ășnica” em preto e branco, para a qual algumas cĂ©lulas jĂĄ adivinharam, Ă© uma tarefa muito difĂ­cil. Ela conheceu em lugares como:


  • Quartas de final do ACM ICPC 2006 - vocĂȘ pode tentar resolver o problema sozinho . O limite de tempo Ă© de 1 segundo, o limite para o nĂșmero de grupos Ă© 400 e o comprimento da sĂ©rie tambĂ©m Ă© 400. Ele possui um nĂ­vel muito alto de complexidade em comparação com outras tarefas.
  • OlimpĂ­ada Internacional de InformĂĄtica 2016 - condição para passar a tarefa . O limite de tempo Ă© de 2 segundos, o limite do nĂșmero de grupos Ă© 100, o comprimento da linha Ă© 200'000. Essas limitaçÔes sĂŁo um exagero, mas a solução de um problema com restriçÔes do ACM ganha 80/100 pontos nesse problema. Aqui, tambĂ©m, o nĂ­vel nĂŁo decepcionou, crianças de todo o mundo com um nĂ­vel de QI cruel treinam hĂĄ vĂĄrios anos para resolver truques diferentes, depois vĂŁo para esta OlimpĂ­ada (apenas 4 pessoas do paĂ­s devem passar), decidem 2 rodadas de 5 horas e, no caso da vitĂłria Ă©pica (bronze em anos diferentes, para 138-240 pontos em 600), a admissĂŁo em Oxford e, em seguida, oferece de empresas claras ao departamento de busca, uma vida longa e feliz abraçada com dakimakura.

A linha Ășnica monocromĂĄtica tambĂ©m pode ser resolvida de diferentes maneiras e, por O ( N ∗ 2 N ) (enumerando todas as opçÔes, verificando a correção, selecionando cĂ©lulas que tĂȘm a mesma cor em todas as opçÔes) e, de alguma forma, menos estĂșpida.


A idéia principal é usar um anålogo de retorno, mas sem cålculos desnecessårios. Se de alguma forma configuramos vårios grupos e agora estamos em algum tipo de célula, precisamos descobrir se é possível colocar os demais grupos, começando na célula atual.


Pseudo cĂłdigo
 def EpicWin(group, cell): if cell >= last_cell: #   return group == group_size win = False #        if group < group_size #    and CanInsertBlack(cell, len[group]) #    and CanInsertWhite(cell + len[group], 1) #    and EpicWin(group + 1, cell + len[group] + 1): #    win = True can_place_black[cell .. (cell + len[group] - 1)] = True can_place_white[cell + len[group]] = True; #         if CanInsertWhite(cell, 1) and EpicWin(group, cell + 1): win = True can_place_white[cell] = True return win EpicWin(0, 0) 

Essa abordagem é chamada de programação dinùmica . O pseudocódigo é simplificado e até os valores calculados não são armazenados lå.


As CanInsertBlack/CanInsertWhite sĂŁo necessĂĄrias para verificar se Ă© teoricamente possĂ­vel colocar um grupo do tamanho certo no lugar certo. Tudo o que eles precisam fazer Ă© verificar se nĂŁo hĂĄ cĂ©lula “100% branca” (para a primeira função) ou “100% preta” (para a segunda) no intervalo de cĂ©lulas indicado. Para que eles possam trabalhar O ( 1 ) , isso pode ser feito usando valores parciais:


CanInsertBlack
 white_counter = [ 0, 0, ..., 0 ] #   n def PrecalcWhite(): for i in range(0, (n-1)): if is_anyway_white[i]: # 1,  i-  100%  white_counter[i]++ if i > 0: white_counter[i] += white_counter[i - 1] def CanInsertBlack(cell, len): #     [cell, cell + len - 1] ans = white_counter[cell + len - 1] if cell > 0: ans -= white_counter[cell - 1] #  ans -     cell  (cell + len - 1) return ans == 0 

A mesma feitiçaria com somas parciais aguarda uma linha do formulårio


 can_place_black[cell .. (cell + len[group] - 1)] = True 

Aqui podemos, em vez de = True aumentar o nĂșmero em 1. E se precisarmos fazer muitas adiçÔes em um segmento em uma determinada matriz a r r a y e, alĂ©m disso, nĂŁo usamos essa matriz antes de adiçÔes diferentes (eles dizem que essa tarefa foi "resolvida offline") e, em vez de um loop:


 #     for i in range(L, R + 1): array[i]++ 

VocĂȘ pode fazer isso:


 #     array[L]++ array[R + 1]-- #    for i in range(1, n): array[i] += array[i - 1] 

Assim, todo o algoritmo trabalha em O ( k ∗ n ) onde k - o nĂșmero de grupos de cĂ©lulas negras, n É o comprimento da string. E vamos Ă  semifinal do ACM ICPC ou obtemos o bronze do interneur. Solução ACM (Java) . Solução IOI (C ++) .


Uma linha, muitas cores


Quando mudamos para nonogramas multicoloridos, que ainda não estão claros como resolver, aprendemos uma verdade terrível - acontece que todas as colunas são magicamente afetadas por todas as colunas! Isso é mais claro através de um exemplo:



Fonte - MĂ©todos para resolver palavras cruzadas em japonĂȘs


Enquanto os nonogramas de duas cores normalmente ficavam sem ele, eles nĂŁo precisavam olhar para os amigos ortogonais.
A figura mostra que, no exemplo da esquerda, as trĂȘs cĂ©lulas da extrema direita estĂŁo vazias, porque a configuração Ă© interrompida se vocĂȘ colorir essas cĂ©lulas em aquelas cores nas quais vocĂȘ se pinta cor nĂŁo branca.


Mas essa piada Ă© matematicamente decidĂ­vel - cada cĂ©lula deve receber um nĂșmero, onde eu o i-Ă©simo bit significa se essa cĂ©lula pode ser fornecida eu ÂȘ cor. Inicialmente, todas as cĂ©lulas tĂȘm um valor 2 c o r e s - 1 . A decisĂŁo da dinĂąmica nĂŁo mudarĂĄ muito.


VocĂȘ pode observar o seguinte efeito: no mesmo exemplo Ă  esquerda, de acordo com a versĂŁo das linhas, a cĂ©lula na extremidade direita pode ter a cor azul ou branca.
De acordo com a versão das colunas, essa célula tem a cor verde ou branca.
De acordo com a versão de senso comum, essa célula terå apenas a cor branca. E então continuamos a calcular a resposta.


Se considerarmos o bit zero como "branco", o primeiro "azul", o segundo "verde", a linha calcularĂĄ o estado da Ășltima cĂ©lula 011 e a coluna 101 . Isso significa que o estado dessa cĂ©lula Ă© real 011 \ & 101 = 001


Pseudo cĂłdigo
 source = [...] #   result = [0, 0, ..., 0] #   len = [...] #    clrs = [...] #    def CanInsertColor(color, cell, len): for i in range(cell, cell + len): if (source[i] & (1 << color)) == 0: #     color    return False; return True def PlaceColor(color, cell, len): for i in range(cell, cell + len): result[i] |= (1 << color) #   color  OR def EpicWinExtended(group, cell): if cell >= last_cell: #   return group == group_size win = False #        if group < group_size #    and CanInsertColor(clrs[group], cell, len[group]) #    and SequenceCheck() #       ,  #         and EpicWin(group + 1, cell + len[group]): #    win = True PlaceColor(clrs[group], cell, len[group]) PlaceColor(0, cell + len[group], 1) #   -    #         #   -  0 if CanInsertWhite(0, cell, 1) and EpicWinExtended(group, cell + 1): win = True PlaceColor(0, cell, 1) return win 

Muitas linhas, muitas cores


Fazendo constantemente a atualização dos estados de todas as linhas e colunas, descritas no Ășltimo parĂĄgrafo, atĂ© que nĂŁo haja uma Ășnica cĂ©lula com mais de um bit. Em cada iteração, apĂłs atualizar todas as linhas e colunas, atualizamos o estado de todas as cĂ©lulas nelas por meio de AND mĂștuo.


Primeiros resultados


Suponha que nĂŁo escrevemos o cĂłdigo como pica-paus, ou seja, nĂŁo copiamos objetos em lugar nenhum por engano, em vez de passarmos por referĂȘncia, nĂŁo inventamos bicicletas em lugar algum, nĂŁo inventamos bicicletas, para operaçÔes de bit usamos __builtin_popcount e __builtin_ctz ( recursos do gcc ), tudo Ă© limpo e arrumado.


Vamos dar uma olhada na hora do programa, que lĂȘ o quebra-cabeça do arquivo e o resolve completamente. Vale a pena avaliar as vantagens da mĂĄquina na qual todas essas coisas foram escritas e testadas:


Especificação do motor para minha motocicleta clåssica Harley Davidson Modelo B 1932
 RAM - 4GB  - AMD E1-2500,  1400MHz  L1 - 128KiB, 1GHz  L2 - 1MiB, 1GHz  - 2,  - 2   « SoC   »   2013    28  

É claro que esse supercomputador foi escolhido porque as otimizaçÔes nele tĂȘm um efeito maior do que em uma mĂĄquina shaitan voadora.


Portanto, depois de executar nosso algoritmo no mais complicado jogo de palavras cruzadas (de acordo com nonograms.ru), obtemos um resultado não muito bom - de 47 a 60 segundos (isso inclui a leitura de um arquivo e uma solução). Note-se que a "complexidade" no site foi bem calculada - as mesmas palavras cruzadas em todas as versÔes do programa também foram as mais difíceis, e outras palavras-chave mais complexas, na opinião do arquivo, foram mantidas em tempo integral.


Palavras cruzadas cor nĂșmero 9596, a maior dificuldade

Para testes rĂĄpidos, foi feita a funcionalidade do benchmark. Para obter dados para ele, eu usei um script especial e os salvei em um formato que o programa entende. Podemos supor que essas palavras cruzadas sĂŁo uma amostra aleatĂłria; portanto, o benchmark mostraria valores mĂ©dios adequados. AlĂ©m disso, o nĂșmero de algumas das palavras cruzadas mais "complexas" (tambĂ©m as maiores em tamanho e nĂșmero de cores) foi registrado em um script para carregar canetas.



Otimização


Aqui vocĂȘ pode ver as possĂ­veis tĂ©cnicas de otimização que foram feitas (1) durante a gravação de todo o algoritmo, (2) para reduzir o tempo de trabalho de meio minuto para uma fração de segundo, (3) as otimizaçÔes que podem ser Ășteis ainda mais.


No momento da escrita do algoritmo


  • FunçÔes especiais para operaçÔes de bit, no nosso caso __builtin_popcount para contar unidades em uma notação binĂĄria de um nĂșmero e __builtin_ctz para o nĂșmero de zeros antes da primeira unidade menor. Tais funçÔes podem nĂŁo aparecer em alguns compiladores. Para o Windows, esses anĂĄlogos sĂŁo adequados:

Popcount do Windows
 #ifdef _MSC_VER # include <intrin.h> # define __builtin_popcount __popcnt #endif 

  • Organização de matrizes - um tamanho menor Ă© o primeiro. Por exemplo, Ă© melhor usar a matriz [2] [3] [500] [1024] do que [1024] [500] [3] [2].
  • O mais importante Ă© a adequação geral do cĂłdigo e a prevenção de preocupaçÔes desnecessĂĄrias nos cĂĄlculos.

O que reduz o tempo de trabalho


  • O sinalizador -O2 ao compilar.
  • Para nĂŁo lançar a linha / coluna completamente resolvida no algoritmo novamente, vocĂȘ pode definir os sinalizadores para cada linha em um std :: vector / array separado, marcĂĄ-los com uma solução completa e nĂŁo deixar ir, se nĂŁo houver mais nada a resolver.
  • As especificidades de uma solução de repetição mĂșltipla de um problema dinĂąmico sugerem que uma matriz especial que contenha sinalizadores marcando partes jĂĄ "calculadas" do problema seja redefinida toda vez que uma nova solução for feita. No nosso caso, esse Ă© um vetor / array bidimensional, em que a primeira dimensĂŁo Ă© o nĂșmero de grupos, a segunda Ă© a cĂ©lula atual (consulte o pseudocĂłdigo EpicWin acima, onde esse array nĂŁo Ă©, mas a idĂ©ia Ă© clara). Em vez de zerar, vocĂȘ pode fazer isso - vamos ter uma variĂĄvel "timer", e a matriz consiste em nĂșmeros que mostram a Ășltima "hora" em que essa peça foi recontada pela Ășltima vez. Ao iniciar uma nova tarefa, o "timer" aumenta em 1. Em vez de verificar o sinalizador booleano, vocĂȘ deve verificar a igualdade do elemento da matriz e o "timer". Isso Ă© eficaz, especialmente nos casos em que longe de todos os estados possĂ­veis sĂŁo cobertos pelo algoritmo (o que significa que zerar a matriz "jĂĄ consideramos isso" leva um bom tempo).
  • Substituindo operaçÔes simples do mesmo tipo (loops com atribuição, etc.) por anĂĄlogos em STL ou coisas mais adequadas. Às vezes, funciona mais rĂĄpido que uma bicicleta.
  • std::vector<bool> em C ++ compacta todos os valores booleanos em bits individuais em nĂșmeros, o que funciona no acesso um pouco mais lento do que se fosse um valor regular no endereço. Se um programa usa esses valores com muita frequĂȘncia, a substituição de bool por um tipo inteiro pode ter um bom efeito no desempenho.
  • Outras fraquezas podem ser buscadas atravĂ©s dos criadores de perfil e editĂĄ-las. Eu mesmo uso o Valgrind, sua anĂĄlise de desempenho Ă© conveniente para examinar o kCachegrind. Os criadores de perfil sĂŁo incorporados a muitos IDEs.

Essas ediçÔes foram suficientes para obter esses dados no benchmark:


Nonogramas coloridos
 izaron@izaron:~/nonograms/build$ ./nonograms_solver -x ../../nonogram/source2/ -e [2018-08-04 22:57:40.432] [Nonograms] [info] Starting a benchmark... [2018-08-04 22:58:03.820] [Nonograms] [info] Average time: 0.00497556, Median time: 0.00302781, Max time: 0.215925 [2018-08-04 22:58:03.820] [Nonograms] [info] Top 10 heaviest nonograms: [2018-08-04 22:58:03.820] [Nonograms] [info] 0.215925 seconds, file 9596 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.164579 seconds, file 4462 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.105828 seconds, file 15831 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.08827 seconds, file 18353 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.0874717 seconds, file 10590 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.0831248 seconds, file 4649 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.0782811 seconds, file 11922 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.0734325 seconds, file 4655 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.071352 seconds, file 10828 [2018-08-04 22:58:03.820] [Nonograms] [info] 0.0708263 seconds, file 11824 

Nonogramas em preto e branco
 izaron@izaron:~/nonograms/build$ ./nonograms_solver -x ../../nonogram/source/ -e -b [2018-08-04 22:59:56.308] [Nonograms] [info] Starting a benchmark... [2018-08-04 23:00:09.781] [Nonograms] [info] Average time: 0.0095679, Median time: 0.00239274, Max time: 0.607341 [2018-08-04 23:00:09.781] [Nonograms] [info] Top 10 heaviest nonograms: [2018-08-04 23:00:09.781] [Nonograms] [info] 0.607341 seconds, file 5126 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.458932 seconds, file 19510 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.452245 seconds, file 5114 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.19975 seconds, file 18627 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.163028 seconds, file 5876 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.161675 seconds, file 17403 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.153693 seconds, file 12771 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.146731 seconds, file 5178 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.142732 seconds, file 18244 [2018-08-04 23:00:09.781] [Nonograms] [info] 0.136131 seconds, file 19385 

VocĂȘ pode notar que, em mĂ©dia, as palavras cruzadas em preto e branco sĂŁo "mais difĂ­ceis" do que as coloridas. Isso confirma as observaçÔes dos amantes de jogos, que tambĂ©m acreditam que a "cor" Ă© resolvida em mĂ©dia com mais facilidade.


Portanto, sem alteraçÔes radicais (como reescrever todo o cĂłdigo em C ou inserçÔes de assembler com chamada rĂĄpida e abaixar o ponteiro do quadro), vocĂȘ pode obter alto desempenho em um computador muito modesto. O princĂ­pio de Pareto pode ser aplicĂĄvel Ă s otimizaçÔes - verifica-se que a otimização menor influencia fortemente, porque esse trecho de cĂłdigo Ă© crĂ­tico e Ă© chamado com muita frequĂȘncia.


Otimização adicional


Os métodos a seguir podem melhorar muito o desempenho do programa. Alguns deles não funcionam em todos os casos, mas sob certas condiçÔes.


  • Reescrevendo o cĂłdigo no estilo C e outros 1972. SubstituĂ­mos ifstream pelo equivalente analĂłgico, vetores por arrays, aprendemos todas as opçÔes do compilador e lutamos por cada ciclo de clock do processador.
  • Paralelização. Por exemplo, no cĂłdigo, hĂĄ uma parte em que as linhas sĂŁo atualizadas seqĂŒencialmente e as colunas:

bool Puzzle :: UpdateState (...)
  ... if (!UpdateGroupsState(solver, dead_rows, row_groups, row_masks)) { return false; } if (!UpdateGroupsState(solver, dead_cols, col_groups, col_masks)) { return false; } return true; 

Essas funçÔes sĂŁo independentes umas das outras e nĂŁo possuem memĂłria compartilhada, exceto pelo solucionador de variĂĄveis ​​(tipo OneLineSolver), para que vocĂȘ possa criar dois objetos do solucionador (aqui, obviamente, apenas um Ă© solucionador) e iniciar dois threads na mesma função. O efeito Ă© o seguinte: nas palavras cruzadas coloridas, o “mais difĂ­cil” Ă© resolvido duas vezes mais rĂĄpido, em preto e branco o mesmo Ă© um terço mais rĂĄpido e o tempo mĂ©dio aumentou, devido ao custo relativamente alto da criação de threads.


Mas, em geral, eu nĂŁo recomendaria fazĂȘ-lo corretamente no programa atual e usĂĄ-lo com calma - em primeiro lugar, criar threads Ă© uma operação cara, vocĂȘ nĂŁo deve constantemente criar threads para tarefas de microssegundos e, em segundo lugar, com alguma combinação de argumentos do programa, os threads podem se referir a quais memĂłria externa, por exemplo, ao criar imagens de uma solução - isso deve ser levado em consideração e protegido.


Se a tarefa fosse sĂ©ria e eu tivesse muitos dados e mĂĄquinas com vĂĄrios nĂșcleos, iria ainda mais longe - vocĂȘ pode criar vĂĄrios encadeamentos constantes, cada um com seu prĂłprio objeto OneLineSolver e outra estrutura segura de encadeamento que controla a distribuição do trabalho e sob demanda fornece uma referĂȘncia para a prĂłxima linha / coluna da solução. Os threads apĂłs resolver outro problema se voltam para a estrutura novamente para resolver outra coisa. Em princĂ­pio, um problema de nĂŁo programa pode ser iniciado sem concluir o anterior, por exemplo, quando essa estrutura estĂĄ envolvida no AND mĂștuo de todas as cĂ©lulas, e entĂŁo alguns fluxos sĂŁo livres e nĂŁo fazem nada. Mais paralelismo pode ser feito na GPU via CUDA - hĂĄ muitas opçÔes.


  • Usando estruturas de dados clĂĄssicas. Observe que, quando mostrei o pseudocĂłdigo da solução para nonograms de cores, as funçÔes CanInsertColor e PlaceColor nĂŁo funcionam de todo O ( 1 ) , diferentemente dos nonogramas em preto e branco. Parece que isso em um programa:

CanPlaceColor e SetPlaceColor
 bool OneLineSolver::CanPlaceColor(const vector<int>& cells, int color, int lbound, int rbound) { // Went out of the border if (rbound >= cells.size()) { return false; } // We can paint a block of cells with a certain color if and only if it is // possible for all cells to have this color (that means, if every cell // from the block has color-th bit set to 1) int mask = 1 << color; for (int i = lbound; i <= rbound; ++i) { if (!(cells[i] & mask)) { return false; } } return true; } void OneLineSolver::SetPlaceColor(int color, int lbound, int rbound) { // Every cell from the block now can have this color for (int i = lbound; i <= rbound; ++i) { result_cells_[i] |= (1 << color); } } 

Ou seja, funciona para a linha, para O(N) (Posteriormente, haverå uma explicação do significado de um código assim).


Vamos ver como vocĂȘ pode obter a melhor complexidade. Tome CanPlaceColor . Esta função verifica que entre todos os nĂșmeros do vetor no segmento [vinculado,vinculado] nĂșmero de bit cor definido como 1. Equivalente a isso, vocĂȘ pode AND todos os nĂșmeros deste segmento e verifique o nĂșmero do bit cor . Usando o fato de que a operação AND comutativa , bem como soma, mĂ­nimo / mĂĄximo, produto ou operação Xou , para contagem rĂĄpida AND , . :


  1. SQRT-. O(√N) , O(√N) . .
  2. . O(NlogN) , O(logN) . .
  3. (Sparse Table). O(NlogN) , O(1) . .

, - ( O(N) , O(1) ) , , ,  varphi , φ(α,ÎČ)∈{α,ÎČ} , — (, ...)


SetPlaceColor . . SQRT- ( " "). - .


- — .


, — , ? :


  1. . log , , — ( magic number , ). , N=105 , O(N2) 10 , O(NlogN) 0.150 , N , . , ( ): O(N√N) versus O(Nlog2N) . N ( ) — 15-30.
  2. , .

— , - — N , . , ~25% ~11% , . - , , , .


— , . .


  • . , , - . : , , "" ? , , ! . , .
  • (, 1337 1000x1000) . std::bitset , — , .

, . "" . , C++ ( rope , ), hashtable , 3-6 / 4-10 /, unordered_map. , boost.


ROFL Omitting Format Language



, , , -.


ROFL — , "GNU's Not Unix". , , , , , . Omitting — (, , : — ).


, Matroska — 4 [52][4F][46][4C], , , , 3 , — , .


, MIT, — , .



GitHub . , , (, ). Magick++ args .


, ( ). nonograms.ru , , .


nonograms.ru .. KyberPrizrak , , , ! nonograms.ru, .


.


Source: https://habr.com/ru/post/pt418069/


All Articles