O historiador tributário

9

Introdução

Há um cobrador de impostos que tem problemas para gerenciar os impostos de seu reino: os registros históricos queimaram em um grande incêndio.

Ele quer descobrir quantos passados ​​possíveis poderiam existir em termos de onde o dinheiro atual foi herdado. Felizmente, seu reino é muito simples.

O reino pode ser modelado por uma matriz booleana 2D, onde lrepresenta alguém que herdou dinheiro e Orepresenta alguém que não herdou . Por exemplo:

l O l l

O O O l

l O l O

O O O l

(Sempre será um retângulo)

Na próxima geração, o reino é menor (os lobos são fortes!).

A próxima geração ficaria assim, sobreposta à geração anterior ( xé um espaço reservado para um descendente na próxima geração)

l O l l
 x x x
O O O l
 x x x
l O l O
 x x x
O O O l

Um descendente olhar para os antepassados que são diretamente em torno deles (Assim, o canto superior esquerdo xverá { l, O, O, O}, chamado de bairro retangular Unaligned )

Se apenas um ancestral herdou dinheiro, o descendente herdará dinheiro deles. Se mais de um ancestral herdou dinheiro, eles brigam e o descendente acaba não herdando dinheiro. Se ninguém herdou dinheiro, o descendente não herdará dinheiro.

(Mais de um descendente pode herdar de um ancestral)

Portanto, a próxima geração seria assim:

​
 l l O

 l l O

 l l O
​

Desafio

Entrada

O estado atual da geração, como uma matriz de matrizes de dois valores distintos, em que as matrizes internas têm o mesmo comprimento.

Por exemplo, para o exemplo acima, pode ser:

[
  [True, True, False],
  [True, True, False],
  [True, True, False]
]

Resultado

Um número inteiro que representa o número de gerações anteriores exclusivas em que a próxima geração é a entrada.

Você pode supor que a resposta sempre será menor que 2 ^ 30 - 1. (ou 1073741823).

A geração anterior seria chamada de "pré-imagem" e esse desafio seria contar as pré-imagens .

Pontuação

Esse é um desafio de ; portanto, cada envio será testado no meu computador, e o envio que demorar menos tempo será o vencedor.

Exemplo de entrada e saída

(Onde 1está um descendente que herdou dinheiro e 0é um descendente que não herdou dinheiro)

Entrada:

[[1, 0, 1],
 [0, 1, 0],
 [1, 0, 1]]

Resultado:

4

Entrada:

[[1, 0, 1, 0, 0, 1, 1, 1],
 [1, 0, 1, 0, 0, 0, 1, 0],
 [1, 1, 1, 0, 0, 0, 1, 0],
 [1, 0, 1, 0, 0, 0, 1, 0],
 [1, 0, 1, 0, 0, 1, 1, 1]]

Resultado:

254

Entrada:

[[1, 1, 0, 1, 0, 1, 0, 1, 1, 0],
 [1, 1, 0, 0, 0, 0, 1, 1, 1, 0],
 [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
 [0, 1, 0, 0, 0, 0, 1, 1, 0, 0]]

Resultado:

11567
Artyer
fonte
6
"lOOlLOOOOLLlololoLOLOLOOLOLOLOLL" foi tudo o que vi quando abri a página.
Magic Octopus Urn

Respostas:

4

C ++ usando a biblioteca BuDDy

Parecia uma boa desculpa para brincar com diagramas de decisão binária . O reino é convertido em uma grande fórmula booleana, na qual temos que contar o número de maneiras pelas quais ele pode ser satisfeito. Isso pode (às vezes) ser feito com mais eficiência do que parece.

O reino deve ser dado como um programa constante como uma matriz plana e com dimensões explicitamente dadas. (Uma boa entrada é deixada como uma execução para o leitor :-)

Aqui está o código embaraçosamente simples:

#include <iostream>
#include <bdd.h>

// describe the kingdom here:

constexpr int ROWS = 4;
constexpr int COLS = 10;

constexpr int a[] = {
   1, 1, 0, 1, 0, 1, 0, 1, 1, 0,
   1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
   1, 1, 0, 0, 0, 0, 0, 0, 0, 1,
   0, 1, 0, 0, 0, 0, 1, 1, 0, 0,
};

// end of description

// check dimensions
static_assert(ROWS*COLS*sizeof(int)==sizeof(a),
          "ROWS*COLS must be the number of entries of a");

// dimensions of previous generation
constexpr int R1 = ROWS+1;
constexpr int C1 = COLS+1;

// condition that exactly one is true
bdd one(bdd a, bdd b, bdd c, bdd d){
  bdd q = a & !b & !c & !d;
  q |= !a & b & !c & !d;
  q |= !a & !b & c & !d;
  q |= !a & !b & !c & d;
  return q;
}

int main()
{
  bdd_init(1000000, 10000); // tuneable, but not too important
  bdd_setvarnum(R1*C1);
  bdd q { bddtrue };
  for(int j=COLS-1; j>=0; j--) // handle high vars first
    for (int i=ROWS-1; i>=0; i--){
      int x=i+R1*j;
      bdd p=one(bdd_ithvar(x), bdd_ithvar(x+1),
                bdd_ithvar(x+R1), bdd_ithvar(x+R1+1));
      if (!a[COLS*i+j])
        p = !p;
      q &= p;
    }
  std::cout << "There are " << bdd_satcount(q) << " preimages\n";
  bdd_done();
}

Para compilar com o debian 8 (jessie), instale libbdd-deve faça g++ -std=c++11 -o hist hist.cpp -lbdd. (As opções de otimização quase não fazem diferença porque o trabalho real é feito na biblioteca.)

Grandes exemplos podem levar a mensagens sobre coleta de lixo. Eles podem ser suprimidos, mas eu prefiro vê-los.

bdd_satcountretorna a double, mas isso é bom o suficiente para o intervalo esperado de resultados. A mesma técnica de contagem é possível com números inteiros exatos (grandes).

O código está otimizado para ROWS<COLS. Se você tiver muito mais linhas do que colunas, pode ser uma boa ideia transpor a matriz.

Peneiradores cristãos
fonte
2,39 segundos. É a metade do tempo que eu tive! Marcando isso como aceito.
Artyer 31/01/19
11
@Artyer: Você gostaria de publicar seu caso de teste oculto mais antigo? Assim como sua solução, se desejar.
Andrew Epstein
@ AndrewEpstein Recentemente, tive uma falha no disco rígido e perdi o código e os casos de teste originais (havia centenas deles, e eles tinham no máximo 300 de largura e 10 de altura). Desculpa.
Artyer 12/02
3

Python 2.7

Esta é apenas uma primeira tentativa ingênua. Não é particularmente rápido, mas está correto.

A primeira observação é que cada célula depende exatamente de quatro células na geração anterior. Podemos representar essas quatro células como um número de 4 bits (0-15). De acordo com as regras, se exatamente uma célula vizinha da geração anterior for 1, então uma determinada célula na geração atual será 1, caso contrário, será 0. Esses correspondem aos poderes de dois, a saber [1, 2, 4, 8],. Quando os quatro ancestrais são representados como um número de 4 bits, qualquer outro número resultará em um 0na geração atual. Com essas informações, ao ver uma célula na geração atual, podemos reduzir as possibilidades do bairro na geração anterior para uma das quatro ou uma das doze possibilidades, respectivamente.

Eu escolhi representar o bairro da seguinte maneira:

32
10

onde 0 é o bit menos significativo e assim por diante.

A segunda observação é que, para duas células adjacentes na geração atual, os dois bairros da geração anterior se sobrepõem:

32  32
10  10

ou:

32
10

32
10

No caso horizontal, o 2bairro da esquerda se sobrepõe ao 3bairro da direita e, da mesma forma, 0à esquerda e 1à direita. No caso vertical, a 1vizinhança da parte superior se sobrepõe à vizinhança 3da parte inferior e da mesma forma que 0na parte superior e 2na parte inferior.

Essa sobreposição significa que podemos reduzir as possibilidades de bairros ainda não escolhidos com base no que já escolhemos. O código funciona da esquerda para a direita, de cima para baixo, em uma pesquisa recursiva em profundidade, para possíveis pré-imagens. O diagrama a seguir indica quais vizinhanças anteriores devemos considerar ao examinar as possíveis vizinhanças de uma célula atual:

f = free choice
h = only have to look at the neighborhood to the left
v = only have to look at the neighborhood to the top
b = have to look at both left and top neighborhoods

[f, h, h, h, h],
[v, b, b, b, b],
[v, b, b, b, b],
[v, b, b, b, b]

Aqui está o código:

def good_horizontal(left, right):
    if (left & 4) >> 2 != (right & 8) >> 3:
        return False
    if left & 1 != (right & 2) >> 1:
        return False
    return True


def good_vertical(bottom, top):
    if (bottom & 8) >> 3 != (top & 2) >> 1:
        return False
    if (bottom & 4) >> 2 != (top & 1):
        return False
    return True


ones = [1, 2, 4, 8]
zeros = [0, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15]
h = {}
v = {}

for i in range(16):
    h[i] = [j for j in range(16) if good_horizontal(i, j)]
    v[i] = [j for j in range(16) if good_vertical(i, j)]


def solve(arr):
    height = len(arr)
    width = len(arr[0])

    if height == 1 and width == 1:
        if arr[0][0] == 1:
            return 4
        else:
            return 12
    return solve_helper(arr)


def solve_helper(arr, i=0, j=0, partial=None):
    height = len(arr)
    width = len(arr[0])

    if arr[i][j] == 1:
        poss = ones
    else:
        poss = zeros

    if i == height - 1 and j == width - 1:  # We made it to the end of this chain
        if height == 1:
            return sum([1 for p in poss if p in h[partial[-1][-1]]])
        else:
            return sum([1 for p in poss if partial[-2][-1] in v[p] and p in h[partial[-1][-1]]])

    if j == width - 1:
        new_i, new_j = i + 1, 0
    else:
        new_i, new_j = i, j + 1

    if i == 0:
        if j == 0:
            # first call
            return sum([solve_helper(arr, new_i, new_j, [[p]]) for p in poss])
        # still in the first row
        return sum([solve_helper(arr, new_i, new_j, [partial[0] + [p]]) for p in poss if p in h[partial[0][-1]]])
    if j == 0:  # starting a new row
        return sum([solve_helper(arr, new_i, new_j, [r for r in partial + [[p]]]) for p in poss if partial[i - 1][0] in v[p]])
    return sum([solve_helper(arr, new_i, new_j, [r for r in partial[:-1] + ([partial[-1] + [p]])]) for p in poss if p in h[partial[i][-1]] and partial[i - 1][j] in v[p]])

Para executá-lo:

test3 = [
    [1, 1, 0, 1, 0, 1, 0, 1, 1, 0],
    [1, 1, 0, 0, 0, 0, 1, 1, 1, 0],
    [1, 1, 0, 0, 0, 0, 0, 0, 0, 1],
    [0, 1, 0, 0, 0, 0, 1, 1, 0, 0]
]

expected3 = 11567

assert(solve(test3) == expected3)
Andrew Epstein
fonte
11
Está demorando muito para fazer os casos de teste ocultos, então não estou pontuando esse envio. Tente um algoritmo diferente, já que este tem sobre muito alto de uma complexidade de tempo (é O(<something>^n), eu acho.)
Artyer