El historiador de impuestos

9

Introducción

Hay un recaudador de impuestos que tiene algunos problemas para administrar los impuestos de su reino: los registros históricos se han incendiado en un gran incendio.

Quiere saber cuántos pasados ​​posibles podría haber en términos de dónde se heredó el dinero actual. Afortunadamente, su reino es muy simple.

El reino puede ser modelado por una matriz booleana 2D, donde lrepresenta a alguien que ha heredado dinero y Orepresenta a alguien que no lo ha hecho. Por ejemplo:

l O l l

O O O l

l O l O

O O O l

(Siempre será un rectángulo)

En la próxima generación, el reino es más pequeño (¡Los lobos son fuertes!).

La próxima generación se vería así, superpuesta a la generación anterior ( xes un marcador de posición para un descendiente en la próxima generación)

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

Un descendiente se verá en los antepasados que son directamente alrededor de ellos (tanto la parte superior izquierda xverá { l, O, O, O}, llamado un barrio rectangular Unaligned )

Si solo un antepasado ha heredado dinero, el descendiente heredará dinero de ellos. Si más de un antepasado ha heredado dinero, se pelearán y el descendiente terminará no heredando dinero. Si nadie ha heredado dinero, el descendiente no heredará dinero.

(Más de un descendiente puede heredar de un antepasado)

Entonces, la próxima generación se vería así:

​
 l l O

 l l O

 l l O
​

Desafío

Entrada

El estado actual de la generación, como una matriz de matrices de dos valores distintos, donde las matrices internas tienen la misma longitud.

Por ejemplo, para el ejemplo anterior, podría ser:

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

Salida

Un entero que representa el número de generaciones anteriores únicas donde la siguiente generación es la entrada.

Puede suponer que la respuesta siempre será menor que 2 ^ 30 - 1. (o 1073741823).

La generación anterior se llamaría una "preimagen" y este desafío sería contar las preimágenes .

Puntuación

Este es un desafío de , por lo que cada envío se probará en mi computadora y el envío que demore menos será el ganador.

Ejemplo de entrada y salida

(¿Dónde 1está un descendiente que heredó dinero y 0es un descendiente que no heredó dinero)

Entrada:

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

Salida:

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]]

Salida:

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]]

Salida:

11567
Artyer
fuente
66
"lOOlLOOOOLLlololoLOLOLOOLOLOLOLL" es todo lo que vi cuando abrí la página por primera vez.
Urna de pulpo mágico el

Respuestas:

4

C ++ usando la biblioteca BuDDy

Esto parecía una buena excusa para jugar con diagramas de decisión binarios . El reino se convierte en una gran fórmula booleana donde tenemos que contar la cantidad de formas en que se puede satisfacer. Eso puede (a veces) hacerse de manera más eficiente de lo que parece.

El reino debe darse como un programa constante como una matriz plana y dimensiones explícitamente dadas. (Se deja una buena entrada como ejercicio para el lector :-)

Aquí está el código vergonzosamente simple:

#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 con debian 8 (jessie), instálalo libbdd-devy hazlo g++ -std=c++11 -o hist hist.cpp -lbdd. (Las opciones de optimización no hacen casi ninguna diferencia porque el trabajo real se realiza en la biblioteca).

Grandes ejemplos pueden conducir a mensajes sobre recolección de basura. Podrían ser suprimidos, pero prefiero verlos.

bdd_satcountdevuelve a double, pero eso es lo suficientemente bueno para el rango esperado de resultados. La misma técnica de conteo es posible con enteros (grandes) exactos.

El código está optimizado para ROWS<COLS. Si tiene muchas más filas que columnas, puede ser una buena idea transponer la matriz.

Christian Sievers
fuente
2,39 segundos. ¡Esto es la mitad del tiempo que tuve! Marcar esto como aceptado.
Artyer
1
@Artyer: ¿Le gustaría publicar su caso de prueba oculto de mayor duración? Además de su solución, si lo desea.
Andrew Epstein
@ AndrewEpstein Recientemente tuve una falla en el disco duro y perdí tanto el código como los casos de prueba originales (Hubo cientos de ellos, y creo que tenían un máximo de 300 de ancho, 10 de alto, creo). Lo siento.
Artyer
3

Python 2.7

Este es solo un primer intento ingenuo. No es particularmente rápido, pero es correcto.

La primera observación es que cada celda depende exactamente de cuatro celdas en la generación anterior. Podemos representar esas cuatro celdas como un número de 4 bits (0-15). De acuerdo con las reglas, si exactamente hay una celda vecina en la generación anterior 1, entonces una celda dada en la generación actual será 1, de lo contrario, lo será 0. Los que corresponden a las potencias de dos, a saber, [1, 2, 4, 8]. Cuando los cuatro antepasados ​​se representan como un número de 4 bits, cualquier otro número dará como resultado un 0en la generación actual. Con esta información, al ver una celda en la generación actual, podemos reducir las posibilidades del vecindario en la generación anterior a una de cuatro o una de doce posibilidades, respectivamente.

Elegí representar el vecindario de la siguiente manera:

32
10

donde 0 es el bit menos significativo, y así sucesivamente.

La segunda observación es que para dos celdas adyacentes en la generación actual, los dos vecindarios de la generación anterior se superponen:

32  32
10  10

o:

32
10

32
10

En el caso horizontal, el 2vecindario de la izquierda se superpone con el 3del vecindario de la derecha, y de manera similar con el 0de la izquierda y el 1de la derecha. En el caso vertical, el 1vecindario superior se superpone con el 3vecindario inferior, y de manera similar con 0el superior y 2el inferior.

Esta superposición significa que podemos reducir las posibilidades de vecindarios aún no elegidos en función de lo que ya hemos elegido. El código funciona de izquierda a derecha, de arriba a abajo, en una búsqueda recursiva en profundidad de posibles preimágenes. El siguiente diagrama indica qué vecindarios anteriores debemos tener en cuenta al observar los posibles vecindarios de una celda actual:

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]

Aquí está el 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 ejecutarlo:

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
fuente
1
Esto lleva mucho tiempo hacer los casos de prueba ocultos, por lo que no estoy calificando esta presentación. Pruebe un algoritmo diferente, ya que esto tiene una complejidad de tiempo demasiado alta ( O(<something>^n)creo)
Artyer el