Crie um Otimizador de Magnitude Não-Tipográfico ™

12

Um nonograma é um jogo de quebra-cabeça japonês no qual o objetivo é desenhar uma imagem em preto e branco de acordo com uma lista de regiões contíguas, como:

Um exemplo de nonograma com um "lambda".

Defina a magnitude não geográfica de uma linha ou coluna para ser o número de regiões negras contíguas nessa linha ou coluna. Por exemplo, a linha superior tem uma magnitude não geográfica de 1, porque há uma região de 2 quadrados nessa linha. A 8a linha tem uma magnitude não-gráfica de 3 porque possui 2, 2, 1.

Uma linha ou coluna vazia possui uma magnitude não geográfica de 0.


Sua tarefa é escrever um programa que utiliza uma grade de solução para um nonograma e produz uma grade de solução com o menor número possível de quadrados preenchidos, onde cada linha e coluna tem o mesmo magnutídeo não-gráfico da grade de solução fornecida.

Por exemplo, uma grade de não-programa com todos os quadrados preenchidos possui uma magnitude não-geográfica de 1 em cada linha ou coluna:

Um nonograma de 10x10, onde cada quadrado é preenchido.

A mesma magnitude não-geográfica poderia ser alcançada apenas com uma faixa diagonal na grade, reduzindo drasticamente o número de quadrados preenchidos:

Um nonograma de 10x10 com a mesma magnitude não-geográfica que a anterior.


Seu programa receberá uma entrada composta por 50.000 linhas desse arquivo ( arquivo de texto tar.gz de 1,32 MB; descompactado 2,15 MB), cada uma representando uma única grade de solução de nonograma 16 × 16 com quadrados preenchidos aleatoriamente (80% em preto) e produzir outras 50.000 linhas, cada uma contendo a grade de solução otimizada para a grade de entrada correspondente.

Cada grade é representada como uma string base64 com 43 caracteres (codificando quadrados da esquerda para a direita e depois de cima para baixo), e seu programa precisará retornar sua saída no mesmo formato. Por exemplo, a primeira grade do arquivo é E/lu/+7/f/3rp//f799xn/9//2mv//nvj/bt/yc9/40=e é renderizada como:

primeiro exemplo nonogram

A grade começa com a Equal é mapeada 000100, portanto, as seis primeiras células da linha superior são todas brancas, exceto a quarta. O próximo caractere é o /qual é mapeado 111111, então as próximas 6 células são todas pretas - e assim por diante.


Seu programa deve realmente retornar uma grade de solução com as magnitudes não-geográficas corretas para cada um dos 50.000 casos de teste. É permitido retornar a mesma grade que a entrada se nada melhor foi encontrado.

O programa para retornar o menor número total de quadrados preenchidos (em qualquer idioma) é o vencedor, com o código mais curto sendo o desempate.


Painel atual:

  1. 3.637.260 - Sleafar, Java
  2. 7.270.894 - flawr, Matlab
  3. 10.239.288 - Joe Z., Bash
Joe Z.
fonte
1
Eu realmente não vejo o ponto da base 64 de codificação e trabalhar com isso é uma verdadeira dor. Não seria mais fácil fazer apenas linhas de zeros e zeros? Ou codificar a coisa toda como um bitmap?
flawr
@ flawr: Reduz o tamanho do arquivo, principalmente (por um fator de 6 em comparação com apenas 1 e 0). Além disso, os bitmaps seriam ainda mais difíceis de trabalhar.
Joe Z.
Você pode criar uma imagem em preto e branco, fácil de ler / gravar e do mesmo tamanho da codificação b64.
flawr
2
também não é um fã da codificação b64, para entrada e / ou saída. Por que não deixar a E / S em qualquer formato conveniente?
Sparr
1
Supondo que seja, eu deveria ter uma solução ótima a essa hora amanhã.
quintopia 18/01/16

Respostas:

7

Python 2 e PuLP - 2.644.688 quadrados (minimizado idealmente); 10.753.553 quadrados (maximizado da melhor maneira)

Golfe mínimo para 1152 bytes

from pulp import*
x=0
f=open("c","r")
g=open("s","w")
for k,m in enumerate(f):
 if k%2:
    b=map(int,m.split())
    p=LpProblem("Nn",LpMinimize)
    q=map(str,range(18))
    ir=q[1:18]
    e=LpVariable.dicts("c",(q,q),0,1,LpInteger)
    rs=LpVariable.dicts("rs",(ir,ir),0,1,LpInteger)
    cs=LpVariable.dicts("cs",(ir,ir),0,1,LpInteger)
    p+=sum(e[r][c] for r in q for c in q),""
    for i in q:p+=e["0"][i]==0,"";p+=e[i]["0"]==0,"";p+=e["17"][i]==0,"";p+=e[i]["17"]==0,""
    for o in range(289):i=o/17+1;j=o%17+1;si=str(i);sj=str(j);l=e[si][str(j-1)];ls=rs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,"";l=e[str(i-1)][sj];ls=cs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,""
    for r,z in enumerate(a):p+=lpSum([rs[str(r+1)][c] for c in ir])==2*z,""
    for c,z in enumerate(b):p+=lpSum([cs[r][str(c+1)] for r in ir])==2*z,""
    p.solve()
    for r in ir:
     for c in ir:g.write(str(int(e[r][c].value()))+" ")
     g.write('\n')
    g.write('%d:%d\n\n'%(-~k/2,value(p.objective)))
    x+=value(p.objective)
 else:a=map(int,m.split())
print x

(NB: as linhas fortemente recuadas começam com tabulações, não espaços.)

Exemplo de saída: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing

Acontece que problemas como esses são facilmente conversíveis em Programas Lineares Inteiros, e eu precisava de um problema básico para aprender a usar o PuLP - uma interface python para uma variedade de solucionadores de LP - para um projeto meu. Acontece também que o PuLP é extremamente fácil de usar, e o construtor de LP despojado funcionou perfeitamente na primeira vez que o experimentei.

As duas coisas boas sobre o emprego de um solucionador de IP ramificado e vinculado para fazer o trabalho duro de resolver isso para mim (além de não ter que implementar um solucionador de ramificação e vinculado) são:

  • Os solucionadores criados de propósito são realmente rápidos. Este programa resolve todos os 50000 problemas em cerca de 17 horas no meu PC doméstico relativamente baixo. Cada instância levou de 1 a 1,5 segundos para ser resolvida.
  • Eles produzem soluções ótimas garantidas (ou dizem que falharam em fazê-lo). Assim, posso ter certeza de que ninguém vai bater minha pontuação em quadrados (embora alguém possa empatar e me vencer na parte do golfe).

Como usar este programa

Primeiro, você precisará instalar o PuLP. pip install pulpdeve fazer o truque se você tiver o pip instalado.

Em seguida, será necessário colocar o seguinte em um arquivo chamado "c": https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing

Em seguida, execute este programa em qualquer versão posterior do Python 2 do mesmo diretório. Em menos de um dia, você terá um arquivo chamado "s" que contém 50.000 grades não programadas (em formato legível), cada uma com o número total de quadrados preenchidos listados abaixo.

Se você deseja maximizar o número de quadrados preenchidos, altere a LpMinimizelinha 8 para LpMaximize. Você obterá resultados muito parecidos com este: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing

Formato de entrada

Este programa usa um formato de entrada modificado, já que Joe Z. disse que poderíamos recodificar o formato de entrada se gostarmos de um comentário sobre o OP. Clique no link acima para ver como é. Consiste em 10000 linhas, cada uma contendo 16 números. As linhas numeradas pares são as magnitudes das linhas de uma determinada instância, enquanto as linhas numeradas ímpares são as magnitudes das colunas da mesma instância que a linha acima delas. Este arquivo foi gerado pelo seguinte programa:

from bitqueue import *

with open("nonograms_b64.txt","r") as f:
    with open("nonogram_clues.txt","w") as g:
        for line in f:
            q = BitQueue(line.decode('base64'))
            nonogram = []
            for i in range(256):
                if not i%16: row = []
                row.append(q.nextBit())
                if not -~i%16: nonogram.append(row)
            s=""
            for row in nonogram:
                blocks=0                         #magnitude counter
                for i in range(16):
                    if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
                s+=str(blocks)+" "
            print >>g, s
            nonogram = map(list, zip(*nonogram)) #transpose the array to make columns rows
            s=""
            for row in nonogram:
                blocks=0
                for i in range(16):
                    if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
                s+=str(blocks)+" "
            print >>g, s

(Esse programa de recodificação também me deu uma oportunidade extra de testar minha classe BitQueue personalizada que criei para o mesmo projeto mencionado acima. É simplesmente uma fila na qual os dados podem ser enviados como sequências de bits OU bytes e a partir dos quais os dados podem ser exibido um pouco ou um byte de cada vez. Nesse caso, funcionou perfeitamente.)

Recodifiquei a entrada pelo motivo específico de que, para construir um ILP, as informações extras sobre as grades usadas para gerar as magnitudes são perfeitamente inúteis. As magnitudes são as únicas restrições e, portanto, as magnitudes são tudo o que eu precisava acessar.

Construtor ILP Ungolfed

from pulp import *
total = 0
with open("nonogram_clues.txt","r") as f:
    with open("solutions.txt","w") as g:
        for k,line in enumerate(f):
            if k%2:
                colclues=map(int,line.split())
                prob = LpProblem("Nonogram",LpMinimize)
                seq = map(str,range(18))
                rows = seq
                cols = seq
                irows = seq[1:18]
                icols = seq[1:18]
                cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
                rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
                colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)
                prob += sum(cells[r][c] for r in rows for c in cols),""
                for i in rows:
                    prob += cells["0"][i] == 0,""
                    prob += cells[i]["0"] == 0,""
                    prob += cells["17"][i] == 0,""
                    prob += cells[i]["17"] == 0,""
                for i in range(1,18):
                    for j in range(1,18):
                        si = str(i); sj = str(j)
                        l = cells[si][str(j-1)]; ls = rowseps[si][sj]
                        prob += cells[si][sj] <= l + ls,""
                        prob += cells[si][sj] >= l - ls,""
                        prob += cells[si][sj] >= ls - l,""
                        prob += cells[si][sj] <= 2 - ls - l,""
                        l = cells[str(i-1)][sj]; ls = colseps[si][sj]
                        prob += cells[si][sj] <= l + ls,""
                        prob += cells[si][sj] >= l - ls,""
                        prob += cells[si][sj] >= ls - l,""
                        prob += cells[si][sj] <= 2 - ls - l,""
                for r,clue in enumerate(rowclues):
                    prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
                for c,clue in enumerate(colclues):
                    prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""
                prob.solve()
                print "Status for problem %d: "%(-~k/2),LpStatus[prob.status]
                for r in rows[1:18]:
                    for c in cols[1:18]:
                        g.write(str(int(cells[r][c].value()))+" ")
                    g.write('\n')
                g.write('Filled squares for %d: %d\n\n'%(-~k/2,value(prob.objective)))
                total += value(prob.objective)
            else:
                rowclues=map(int,line.split())
print "Total number of filled squares: %d"%total

Este é o programa que realmente produziu a "saída de exemplo" vinculada acima. Daí as cordas extra longas no final de cada grade, que eu truncava ao jogar no golfe. (A versão golfada deve produzir saída idêntica, menos as palavras "Filled squares for ")

Como funciona

cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)

Eu uso uma grade 18x18, com a parte central 16x16 sendo a solução real do quebra-cabeça. cellsé essa grade. A primeira linha cria 324 variáveis ​​binárias: "cell_0_0", "cell_0_1" e assim por diante. Também crio grades dos "espaços" entre e ao redor das células na parte da solução da grade. rowsepsaponta para as 289 variáveis ​​que simbolizam os espaços que separam as células horizontalmente, enquanto da colsepsmesma forma aponta para as variáveis ​​que marcam os espaços que separam as células verticalmente. Aqui está um diagrama unicode:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Os 0s e os são os valores binários rastreados pelas cellvariáveis, os |s são os valores binários rastreados pelas rowsepvariáveis ​​e os -s são os valores binários rastreados pelas colsepvariáveis.

prob += sum(cells[r][c] for r in rows for c in cols),""

Esta é a função objetivo. Apenas a soma de todas as cellvariáveis. Como essas são variáveis ​​binárias, esse é exatamente o número de quadrados preenchidos na solução.

for i in rows:
    prob += cells["0"][i] == 0,""
    prob += cells[i]["0"] == 0,""
    prob += cells["17"][i] == 0,""
    prob += cells[i]["17"] == 0,""

Isso apenas define as células ao redor da borda externa da grade como zero (e é por isso que eu as representei como zeros acima). Essa é a maneira mais conveniente de rastrear quantos "blocos" de células são preenchidos, pois garante que cada alteração de não preenchida para preenchida (movendo-se através de uma coluna ou linha) seja correspondida por uma alteração correspondente de preenchida para não preenchida (e vice-versa ), mesmo que a primeira ou a última célula da linha esteja preenchida. Esse é o único motivo para usar uma grade 18x18 em primeiro lugar. Não é a única maneira de contar blocos, mas acho que é a mais simples.

for i in range(1,18):
    for j in range(1,18):
        si = str(i); sj = str(j)
        l = cells[si][str(j-1)]; ls = rowseps[si][sj]
        prob += cells[si][sj] <= l + ls,""
        prob += cells[si][sj] >= l - ls,""
        prob += cells[si][sj] >= ls - l,""
        prob += cells[si][sj] <= 2 - ls - l,""
        l = cells[str(i-1)][sj]; ls = colseps[si][sj]
        prob += cells[si][sj] <= l + ls,""
        prob += cells[si][sj] >= l - ls,""
        prob += cells[si][sj] >= ls - l,""
        prob += cells[si][sj] <= 2 - ls - l,""

Esta é a verdadeira carne da lógica do ILP. Basicamente, exige que cada célula (exceto as da primeira linha e coluna) seja o xor lógico da célula e o separador diretamente à sua esquerda na linha e diretamente acima dela na coluna. Eu recebi as restrições que simulam um xor em um programa inteiro {0,1} com esta resposta maravilhosa: /cs//a/12118/44289

Para explicar um pouco mais: essa restrição xor permite que os separadores possam ser 1 se e somente se estiverem entre células que são 0 e 1 (marcando uma mudança de não preenchida para preenchida ou vice-versa). Assim, haverá exatamente o dobro de separadores com valor 1 em uma linha ou coluna do número de blocos nessa linha ou coluna. Em outras palavras, a soma dos separadores em uma determinada linha ou coluna é exatamente o dobro da magnitude dessa linha / coluna. Daí as seguintes restrições:

for r,clue in enumerate(rowclues):
    prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
for c,clue in enumerate(colclues):
    prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""

E é isso mesmo. O restante apenas pede ao solucionador padrão que resolva o ILP e formata a solução resultante enquanto a grava no arquivo.

quintopia
fonte
Resposta realmente boa. Faça-me querer aprender sobre solucionadores de LP. Você acha que poderia ser usado para resolver o quebra-cabeça flood it (link) para uma placa de 19x19, 6 cores (sobre o tempo para calcular uma solução)? Eu já respondi a esse concurso (e o venci), mas meu método (algoritmo de busca A *) apenas fornece soluções não ideais.
Tigrou
@tigrou Obrigado. Não sei se o problema da inundação é linear o suficiente para admitir essa solução. Certamente não consigo ver como modelá-lo dessa maneira.
quintopia 10/02
Parece que alguém já tentou: kunigami.blog/2012/09/16/flood-it-an-exact- Approach. No entanto, eles não conseguiram soluções ideais em tempo possível para uma placa 14x14.
tigrou
3

Java, 6.093.092 4.332.656 3.637.260 quadrados (minimizado), 10.567.550 10.567.691 10.568.746 quadrados (maximizado)

Ambas as variantes do programa executam operações repetidamente na grade de origem, sem alterar a magnitude.

Minimizador

encolher()

insira a descrição da imagem aqui

Se um quadrado preto tiver 2 vizinhos brancos e 2 vizinhos pretos em um ângulo de 90 °, ele poderá ser substituído por um quadrado branco.

moveLine ()

insira a descrição da imagem aqui insira a descrição da imagem aqui

Em configurações como acima, a linha preta pode ser movida para a direita. Isso é feito repetidamente para todas as quatro direções da linha no sentido horário e anti-horário, para abrir novas possibilidades de redução.

Maximizer

Remova o comentário da linha main()e comente a linha acima desta versão.

crescer()

insira a descrição da imagem aqui

Se um quadrado branco tiver 2 vizinhos brancos e 2 vizinhos pretos em um ângulo de 90 °, ele poderá ser substituído por um quadrado preto.

moveLine ()

O mesmo que no Minimizer.

Fonte

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.Base64;
import java.util.function.Function;

public class Main {
    private static final int SIZE = 16;
    private static final int SIZE_4 = SIZE + 4;
    private static final int E = 0;
    private static final int N = 1;
    private static final int W = 2;
    private static final int S = 3;

    private static final Base64.Decoder decoder = Base64.getMimeDecoder();
    private static final Base64.Encoder encoder = Base64.getMimeEncoder();
    private static int sourceBlack = 0;
    private static int targetBlack = 0;

    private static class Nonogram {
        private final boolean[] cells = new boolean[SIZE_4 * SIZE_4];
        private final int[] magnitudes;

        public Nonogram(String encoded) {
            super();
            byte[] decoded = decoder.decode(encoded);
            for (int i = 0; i < decoded.length; ++ i) {
                for (int j = 0; j < 8; ++ j) {
                    if ((decoded[i] & (1 << (7 - j))) != 0) {
                        int k = i * 8 + j;
                        cells[getPos(k / SIZE, k % SIZE)] = true;
                        ++ sourceBlack;
                    }
                }
            }
            magnitudes = calcMagnitudes();
        }

        private int getPos(int row, int col) {
            return (row + 2) * SIZE_4 + col + 2;
        }

        private int move(int pos, int dir, int count) {
            switch (dir) {
                case E: return pos + count;
                case N: return pos - count * SIZE_4;
                case W: return pos - count;
                case S: return pos + count * SIZE_4;
                default: return pos;
            }
        }

        private int move(int pos, int dir) {
            return move(pos, dir, 1);
        }

        private int[] calcMagnitudes() {
            int[] result = new int[SIZE * 2];
            for (int row = 0; row < SIZE; ++ row) {
                for (int col = 0; col < SIZE; ++ col) {
                    int pos = getPos(row, col);
                    if (cells[pos]) {
                        if (!cells[move(pos, W)]) {
                            ++ result[row + SIZE];
                        }
                        if (!cells[move(pos, N)]) {
                            ++ result[col];
                        }
                    }
                }
            }
            return result;
        }

        private boolean isBlack(int pos) {
            return cells[pos];
        }

        private boolean isWhite(int pos) {
            return !cells[pos];
        }

        private boolean allBlack(int pos, int dir, int count) {
            int p = pos;
            for (int i = 0; i < count; ++ i) {
                if (isWhite(p)) {
                    return false;
                }
                p = move(p, dir);
            }
            return true;
        }

        private boolean allWhite(int pos, int dir, int count) {
            int p = pos;
            for (int i = 0; i < count; ++ i) {
                if (isBlack(p)) {
                    return false;
                }
                p = move(p, dir);
            }
            return true;
        }

        private int findWhite(int pos, int dir) {
            int count = 0;
            int p = pos;
            while (cells[p]) {
                ++ count;
                p = move(p, dir);
            }
            return count;
        }

        @SafeVarargs
        private final void forEach(Function<Integer, Boolean>... processors) {
            outer:
            for (;;) {
                for (Function<Integer, Boolean> processor : processors) {
                    for (int row = 0; row < SIZE; ++ row) {
                        for (int col = 0; col < SIZE; ++ col) {
                            if (processor.apply(getPos(row, col))) {
                                continue outer;
                            }
                        }
                    }
                }
                return;
            }
        }

        private boolean shrink(int pos) {
            if (cells[pos] && cells[move(pos, W)] != cells[move(pos, E)] &&
                    cells[move(pos, N)] != cells[move(pos, S)]) {
                cells[pos] = false;
                return true;
            }
            return false;
        }

        private boolean grow(int pos) {
            if (!cells[pos] && cells[move(pos, W)] != cells[move(pos, E)] &&
                    cells[move(pos, N)] != cells[move(pos, S)]) {
                cells[pos] = true;
                return true;
            }
            return false;
        }

        private boolean moveLine(boolean clockwise, int dir, int sourcePos) {
            int from = (dir + (clockwise ? 1 : 3)) % 4;
            int to = (dir + (clockwise ? 3 : 1)) % 4;
            int opp = (dir + 2) % 4;
            if (isBlack(sourcePos) && isWhite(move(sourcePos, from)) && isWhite(move(sourcePos, dir))) {
                int toCount = findWhite(move(move(sourcePos, dir), to), to) + 1;
                if (allWhite(move(sourcePos, to), to, toCount + 1)) {
                    int lineCount = 1;
                    int tmpPos = move(sourcePos, opp);
                    while (isBlack(tmpPos) && isWhite(move(tmpPos, from)) && allWhite(move(tmpPos, to),  to, toCount + 1)) {
                        ++ lineCount;
                        tmpPos = move(tmpPos, opp);
                    }
                    if (allBlack(tmpPos, to, toCount + 1)) {
                        tmpPos = sourcePos;
                        for (int j = 0; j < lineCount; ++ j) {
                            cells[tmpPos] = false;
                            cells[move(tmpPos, to, toCount)] = true;
                            tmpPos = move(tmpPos, opp);
                        }
                        return true;
                    }
                }
            }
            return false;
        }

        public Nonogram minimize() {
            for (int i = 0; i < 5; ++ i) {
                forEach(pos -> shrink(pos), pos -> moveLine(true, E, pos), pos -> moveLine(true, N, pos),
                        pos -> moveLine(true, W, pos), pos -> moveLine(true, S, pos));
                forEach(pos -> shrink(pos), pos -> moveLine(false, E, pos), pos -> moveLine(false, N, pos),
                        pos -> moveLine(false, W, pos), pos -> moveLine(false, S, pos));
            }
            return this;
        }

        public Nonogram maximize() {
            for (int i = 0; i < 5; ++ i) {
                forEach(pos -> grow(pos), pos -> moveLine(true, E, pos), pos -> moveLine(true, N, pos),
                        pos -> moveLine(true, W, pos), pos -> moveLine(true, S, pos));
                forEach(pos -> grow(pos), pos -> moveLine(false, E, pos), pos -> moveLine(false, N, pos),
                        pos -> moveLine(false, W, pos), pos -> moveLine(false, S, pos));
            }
            return this;
        }

        public String toBase64() {
            if (!Arrays.equals(magnitudes, calcMagnitudes())) {
                throw new RuntimeException("Something went wrong!");
            }
            byte[] decoded = new byte[SIZE * SIZE / 8];
            for (int i = 0; i < decoded.length; ++ i) {
                for (int j = 0; j < 8; ++ j) {
                    int k = i * 8 + j;
                    if (cells[getPos(k / SIZE, k % SIZE)]) {
                        decoded[i] |= 1 << (7 - j);
                        ++ targetBlack;
                    }
                }
            }
            return encoder.encodeToString(decoded);
        }

        @Override
        public String toString() {
            StringBuilder b = new StringBuilder();
            for (int row = 0; row < SIZE; ++ row) {
                for (int col = 0; col < SIZE; ++ col) {
                    b.append(cells[getPos(row, col)] ? '#' : ' ');
                }
                b.append('\n');
            }
            return b.toString();
        }
    }

    public static void main(String[] args) throws Exception {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("solutions_b64.txt"));
                BufferedReader reader = new BufferedReader(new FileReader("nonograms_b64.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(new Nonogram(line).minimize().toBase64() + "\n");
                //writer.write(new Nonogram(line).maximize().toBase64() + "\n");
            }
        }
        System.out.printf("%d -> %d", sourceBlack, targetBlack);
    }
}
Sleafar
fonte
1

Bash - 10.239.288 quadrados

Como uma solução de referência de último lugar:

cp nonograms_b64.txt solutions_b64.txt

Como seu programa tem permissão para retornar a mesma grade se não encontrar uma solução melhor, imprimir todo o arquivo literalmente também é válido.

Há um total de 10.239.288 quadrados pretos no arquivo de teste, que é bem próximo dos 10.240.000 que você esperaria de 80% dos quadrados preenchidos em 50.000 grades com 256 quadrados cada. Como sempre, com minhas perguntas sobre bateria de teste, selecionei o número de casos de teste com a expectativa de que as pontuações ideais estejam em torno de 2 milhões, embora eu suspeite que as pontuações estejam mais próximas de 4 ou 5 milhões desta vez. .


Se alguém puder criar uma solução que maximize os quadrados pretos em vez de minimizá-los e consiga superar 10.240.000, eu poderia considerar dar uma recompensa.

Joe Z.
fonte
1

Matlab, 7.270.894 quadrados (~ 71% do original)

A idéia é uma pesquisa repetida simples e gananciosa: para cada quadrado preto, tente se você pode defini-lo para branco sem alterar as magnitudes não-geográficas. Repita isso duas vezes. (Você pode obter resultados muito melhores com mais repetições, mas não de graça: resulta em um tempo de execução correspondentemente mais longo. Agora, eram cerca de 80 minutos. Eu faria isso, se não precisássemos calcular todos os casos de teste de 50 mil ...)

Aqui o código (cada função em um arquivo separado, como de costume.)

function D = b64decode(E)
% accepts a string of base 64 encoded data, and returns a array of zeros
% and ones
F = E;
assert( mod(numel(E),4)==0 && 0 <= sum(E=='=') && sum(E=='=') <= 2,'flawed base 64 code')

F('A' <= E & E<= 'Z') = F('A' <= E & E<= 'Z') -'A';       %upper case
F('a' <= E & E<= 'z') = F('a' <= E & E<= 'z') -'a' + 26;  %lower case
F('0'<= E & E <= '9') = F('0'<= E & E <= '9') -'0' + 52;  %digits
F( E == '+') = 62;
F( E == '/') = 63;
F( E == '=') = 0;

D=zeros(1,numel(E)*3*8/4);

for k=1:numel(E);
    D(6*(k-1)+1 + (0:5)) = dec2bin(F(k),6)-'0';
end

if E(end) == '=';
    D(end-7:end) = [];
    if E(end-1) == '=';
        D(end-7:end) = [];
    end
end
end

function E = b64encode(D)
assert(mod(numel(D),8)==0,'flawed byte code');
N=0;
while mod(numel(D),6) ~= 0;
    D = [D,zeros(1,8)];
    N = N+1;
end
dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

E=zeros(1,numel(D)/6)+'=';
for k=0:numel(E)-N-1;
    E(k+1) = dict(bin2dec( [D(6*k+(1:6))+'0',''] ) + 1);
end

E = [E,''];
end


function [B,T,O] = reduce_greedy(N)
% reduce greedily
NM = nomographic_magnitude(N);
B = N; %current best
M = N;
T = nnz(N); %current number of filled squares
O = T;

for r=1:2;  %how many repetitions
    I = find(B);
    for k=1:numel(I);
        M = B;
        M( I(k) ) = 0;
        %check whether we have a valid solution
        if all(NM == nomographic_magnitude(M))
            if T > nnz(M); %did we actually reduce the number of filled squares?
                B = M;
                T = nnz(M);
            end
        end
    end
end


%% main file
string_in = fileread('nonograms_b64.txt');
string_out = string_in;
tic
total_new = 0;  %total number of black squares
total_old = 0;
M = 50000;
for k=1:M;
    disp(k/M); %display the progress
    line = string_in(45*(k-1)+(1:44));
    decoded = b64decode(line);        
    nonogram = reshape(decoded,16,[]) ;%store nonogram as a matrix
    [B,T,O] = reduce_greedy(nonogram);
    disp([nnz(B),nnz(nonogram)])
    total_new = total_new + T;
    total_old = total_old + O;
    string_in(45*(k-1)+(1:44)) = b64encode(B(:).');
end
toc
F = fopen('nonograms_b64_out.txt','w');
fprintf(F,string_out);
%%
disp([total_new,total_old])
flawr
fonte