Lo storico fiscale


9

introduzione

C'è un esattore delle tasse che ha qualche problema a gestire le tasse del suo regno: i documenti storici sono stati bruciati in un grande incendio.

Vuole scoprire quanti possibili passati potrebbero esserci in termini di provenienza del denaro attuale. Fortunatamente, il suo regno è molto semplice.

Il regno può essere modellato da una matrice booleana 2D, dove lrappresenta qualcuno che ha ereditato il denaro e Orappresenta qualcuno che non lo ha fatto. Per esempio:

l O l l

O O O l

l O l O

O O O l

(Sarà sempre un rettangolo)

Nella prossima generazione, il regno è più piccolo (I lupi sono forti!).

La prossima generazione sarebbe simile a questa, sovrapposta alla generazione precedente ( xè un segnaposto per un discendente nella prossima generazione)

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 discendente esaminerà gli antenati che sono direttamente intorno a loro (Così in alto a sinistra xvedrà { l, O, O, O}, chiamato quartiere rettangolare Unaligned )

Se solo un antenato ha ereditato denaro, il discendente erediterà il denaro da loro. Se più di un antenato ha ereditato denaro, si borbotteranno e il discendente finirà per non ereditare denaro. Se nessuno ha ereditato denaro, il discendente non erediterà denaro.

(Più di un discendente può ereditare da un antenato)

Quindi, la prossima generazione sarebbe simile a:

​
 l l O

 l l O

 l l O
​

Sfida

Ingresso

Lo stato corrente della generazione, come una matrice di matrici di due valori distinti, in cui le matrici interne sono tutte della stessa lunghezza.

Ad esempio, per l'esempio sopra, potrebbe essere:

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

Produzione

Un numero intero che rappresenta il numero di generazioni precedenti univoche in cui la generazione successiva è l'input.

Puoi presumere che la risposta sarà sempre inferiore a 2 ^ 30 - 1. (o 1073741823).

La generazione precedente si chiamerebbe "pre-immagine" e questa sfida sarebbe quella di contare le preimmagini .

punteggio

Questa è una sfida con il , quindi ogni invio verrà testato sul mio computer e l'invio che richiede meno tempo sarà il vincitore.

Esempio di input e output

(Dov'è 1un discendente che ha ereditato il denaro ed 0è un discendente che non ha ereditato il denaro)

Ingresso:

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

Produzione:

4

Ingresso:

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

Produzione:

254

Ingresso:

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

Produzione:

11567

6
"lOOlLOOOOLLlololoLOLOLOOLOLOLOLL" è tutto ciò che ho visto la prima volta che ho aperto la pagina.
Magic Octopus Urn

Risposte:


4

C ++ utilizzando la libreria BuDDy

Sembrava una buona scusa per giocare con i diagrammi decisionali binari . Il regno viene convertito in una grande formula booleana in cui dobbiamo contare il numero di modi in cui può essere soddisfatto. Ciò può (a volte) essere eseguito in modo più efficiente di quanto sembri.

Il regno deve essere dato come costante di programma come matrice piatta e dimensioni esplicitamente date. (Un bel input viene lasciato come esecuzione per il lettore :-)

Ecco il codice imbarazzantemente semplice:

#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();
}

Per compilare con debian 8 (jessie), installa libbdd-deve fai g++ -std=c++11 -o hist hist.cpp -lbdd. (L'ottimizzazione delle opzioni non fa quasi alcuna differenza perché il vero lavoro viene svolto nella libreria.)

Grandi esempi possono portare a messaggi sulla raccolta dei rifiuti. Potrebbero essere soppressi, ma preferisco vederli.

bdd_satcountrestituisce a double, ma è abbastanza buono per l'intervallo atteso di risultati. La stessa tecnica di conteggio è possibile con numeri interi esatti (grandi).

Il codice è ottimizzato per ROWS<COLS. Se hai molte più righe delle colonne, potrebbe essere una buona idea trasporre la matrice.


2,39 secondi. Questa è la metà delle volte che ho avuto! Contrassegnando questo come accettato.
Artyer,

1
@Artyer: ti andrebbe di pubblicare il tuo caso di test nascosto più longevo? Oltre alla tua soluzione, se lo desideri.
Andrew Epstein,

@AndrewEpstein Di recente ho avuto un guasto al disco rigido e ho perso sia il codice che i casi di test originali (ce n'erano centinaia, ed erano max 300, 10 alti). Scusate.
Artyer,

3

Python 2.7

Questo è solo un primo tentativo ingenuo. Non è particolarmente veloce, ma è corretto.

La prima osservazione è che ogni cellula dipende esattamente da quattro cellule della generazione precedente. Possiamo rappresentare quelle quattro celle come un numero di 4 bit (0-15). Secondo le regole, se esattamente è una cella vicina nella generazione precedente 1, allora una data cella nella generazione corrente sarà 1, altrimenti lo sarà 0. Quelli corrispondono alle potenze di due, cioè, [1, 2, 4, 8]. Quando i quattro antenati sono rappresentati come un numero a 4 bit, qualsiasi altro numero comporterà a 0nella generazione corrente. Con queste informazioni, dopo aver visto una cellula nella generazione attuale, possiamo restringere le possibilità del vicinato nella generazione precedente rispettivamente a una delle quattro o una delle dodici possibilità.

Ho scelto di rappresentare il quartiere come segue:

32
10

dove 0 è il bit meno significativo e così via.

La seconda osservazione è che per due celle adiacenti nell'attuale generazione, i due quartieri della generazione precedente si sovrappongono:

32  32
10  10

o:

32
10

32
10

Nel caso orizzontale, il 2vicinato di sinistra si sovrappone con il 3vicinato di destra, e allo stesso modo con 0il sinistro e 1il destro. Nel caso verticale, la 1parte superiore del quartiere superiore si sovrappone alla 3parte inferiore del quartiere e allo stesso modo con la 0parte superiore e la 2parte inferiore.

Questa sovrapposizione significa che possiamo restringere le possibilità di quartieri non ancora scelti in base a ciò che abbiamo già scelto. Il codice funziona da sinistra a destra, dall'alto verso il basso, in una ricerca ricorsiva in profondità per possibili preimmagini. Il diagramma seguente indica quali quartieri precedenti dobbiamo considerare quando guardiamo i possibili quartieri di una cella corrente:

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]

Ecco il codice:

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

Per eseguirlo:

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)

1
Ci vuole troppo tempo per fare i casi di test nascosti, quindi non sto segnando questo invio. Prova un algoritmo diverso, dato che questo ha una complessità temporale troppo elevata ( O(<something>^n)credo).
Artyer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.