Der Steuerhistoriker


9

Einführung

Es gibt einen Steuereintreiber, der Schwierigkeiten hat, die Steuern seines Königreichs zu verwalten: Die historischen Aufzeichnungen sind in einem großen Feuer niedergebrannt.

Er möchte herausfinden, wie viele mögliche Vergangenheiten es geben könnte, woher das aktuelle Geld stammt. Zum Glück ist sein Königreich sehr einfach.

Das Königreich kann durch eine 2D-Boolesche Matrix modelliert werden, ldie jemanden darstellt, der Geld geerbt hat, und Ojemanden darstellt, der dies nicht getan hat. Beispielsweise:

l O l l

O O O l

l O l O

O O O l

(Es wird immer ein Rechteck sein)

In der nächsten Generation ist das Königreich kleiner (Die Wölfe sind stark!).

Die nächste Generation würde so aussehen, überlagert die vorherige Generation ( xist ein Platzhalter für einen Nachkommen in der nächsten Generation).

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

Ein Nachkomme an den Vorfahren sucht, die direkt um sie herum sind (also oben links xsehen { l, O, O, O}, eine genannter Unaligned rechteckige Nachbarschaft )

Wenn nur ein Vorfahr Geld geerbt hat, erbt der Nachkomme Geld von ihnen. Wenn mehr als ein Vorfahr Geld geerbt hat, streiten sie sich und der Nachkomme erbt kein Geld. Wenn niemand Geld geerbt hat, erbt der Nachkomme kein Geld.

(Mehr als ein Nachkomme kann von einem Vorfahren erben)

Die nächste Generation würde also so aussehen:

​
 l l O

 l l O

 l l O
​

Herausforderung

Eingang

Der aktuelle Status der Generation als Array von Arrays mit zwei unterschiedlichen Werten, wobei die inneren Arrays alle dieselbe Länge haben.

Für das obige Beispiel könnte es beispielsweise sein:

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

Ausgabe

Eine Ganzzahl, die die Anzahl der eindeutigen vorherigen Generationen darstellt, wobei die nächste Generation die Eingabe ist.

Sie können davon ausgehen, dass die Antwort immer kleiner als 2 ^ 30 - 1 ist (oder 1073741823).

Die vorherige Generation würde als "Vorbild" bezeichnet, und diese Herausforderung würde darin bestehen, die Vorbilder zu zählen .

Wertung

Dies ist eine Herausforderung mit dem Daher wird jede Einreichung auf meinem Computer getestet, und die Einreichung, die am wenigsten Zeit in Anspruch nimmt, ist der Gewinner.

Beispiel für Ein- und Ausgabe

(Wo 1ist ein Nachkomme, der Geld geerbt hat, und 0ein Nachkomme, der kein Geld geerbt hat?)

Eingang:

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

Ausgabe:

4

Eingang:

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

Ausgabe:

254

Eingang:

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

Ausgabe:

11567

6
"lOOlLOOOOLLlololoLOLOLOOLOLOLL" war alles, was ich sah, als ich die Seite zum ersten Mal öffnete.
Magic Octopus Urn

Antworten:


4

C ++ mit der BuDDy-Bibliothek

Dies schien eine gute Ausrede zu sein, um mit binären Entscheidungsdiagrammen zu spielen . Das Königreich wird in eine große boolesche Formel umgewandelt, in der wir die Anzahl der Möglichkeiten zählen müssen, wie es erfüllt werden kann. Das kann (manchmal) effizienter gemacht werden, als es sich anhört.

Das Königreich muss als Programmkonstante als flaches Array und explizit vorgegebene Dimensionen angegeben werden. (Netter Input bleibt als Execise für den Leser :-)

Hier ist der peinlich einfache Code:

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

Zum Kompilieren mit Debian 8 (Jessie) installieren libbdd-devund ausführen g++ -std=c++11 -o hist hist.cpp -lbdd. (Optimierungsoptionen machen fast keinen Unterschied, da die eigentliche Arbeit in der Bibliothek erledigt wird.)

Große Beispiele können zu Nachrichten über die Speicherbereinigung führen. Sie könnten unterdrückt werden, aber ich sehe sie lieber.

bdd_satcountgibt a zurück double, aber das ist gut genug für den erwarteten Ergebnisbereich. Die gleiche Zähltechnik ist mit exakten (großen) ganzen Zahlen möglich.

Der Code ist optimiert für ROWS<COLS. Wenn Sie viel mehr Zeilen als Spalten haben, ist es möglicherweise eine gute Idee, die Matrix zu transponieren.


2,39 Sekunden. Dies ist die halbe Zeit, die ich hatte! Markieren Sie dies als akzeptiert.
Artyer

1
@Artyer: Möchten Sie Ihren am längsten laufenden versteckten Testfall veröffentlichen? Sowie Ihre Lösung, wenn Sie möchten.
Andrew Epstein

@ AndrewEpstein Ich hatte kürzlich einen Festplattenfehler und verlor sowohl den Code als auch die ursprünglichen Testfälle (es gab Hunderte von ihnen, und sie waren maximal 300 breit, 10 hoch, denke ich). Es tut uns leid.
Artyer

3

Python 2.7

Dies ist nur ein naiver erster Versuch. Es ist nicht besonders schnell, aber es ist richtig.

Die erste Beobachtung ist, dass jede Zelle von genau vier Zellen der vorherigen Generation abhängig ist. Wir können diese vier Zellen als 4-Bit-Zahl (0-15) darstellen. Gemäß den Regeln ist 1eine bestimmte Zelle in der aktuellen Generation , wenn genau eine benachbarte Zelle in der vorherigen Generation vorhanden ist 1, andernfalls 0. Diese entsprechen den Potenzen von zwei, nämlich [1, 2, 4, 8]. Wenn die vier Vorfahren als 4-Bit-Zahl dargestellt werden, führt jede andere Zahl zu einer 0in der aktuellen Generation. Mit diesen Informationen können wir, wenn wir eine Zelle in der aktuellen Generation sehen, die Möglichkeiten der Nachbarschaft in der vorherigen Generation auf eine von vier bzw. eine von zwölf Möglichkeiten eingrenzen.

Ich habe mich entschieden, die Nachbarschaft wie folgt darzustellen:

32
10

Dabei ist 0 das niedrigstwertige Bit und so weiter.

Die zweite Beobachtung ist, dass sich für zwei benachbarte Zellen in der aktuellen Generation die beiden Nachbarschaften der vorherigen Generation überlappen:

32  32
10  10

oder:

32
10

32
10

Im horizontalen Fall 2überlappt sich die Nachbarschaft von links mit der Nachbarschaft 3von rechts und in ähnlicher Weise mit der 0von links und 1rechts. Im vertikalen Fall 1überlappt sich die Nachbarschaft von oben mit der Nachbarschaft 3von unten und in ähnlicher Weise mit der Nachbarschaft 0oben und 2unten.

Diese Überlappung bedeutet, dass wir die Möglichkeiten für noch nicht ausgewählte Stadtteile basierend auf dem, was wir bereits ausgewählt haben, eingrenzen können. Der Code funktioniert von links nach rechts, von oben nach unten, in einer rekursiven Tiefensuche nach möglichen Vorbildern. Das folgende Diagramm zeigt, welche vorherigen Nachbarschaften wir berücksichtigen müssen, wenn wir die möglichen Nachbarschaften einer aktuellen Zelle betrachten:

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]

Hier ist der Code:

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

Um es auszuführen:

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
Das Ausführen der versteckten Testfälle dauert zu lange, daher bewerte ich diese Einreichung nicht. Versuchen Sie es mit einem anderen Algorithmus, da dieser eine zu hohe zeitliche Komplexität aufweist ( O(<something>^n)ich glaube.)
Artyer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.