Податковий історик


9

Вступ

Є збирач податків, який має певні проблеми з управлінням податками свого царства: історичні записи згоріли у великому пожежі.

Він хоче з'ясувати, скільки можливих минулих моментів може відбутися з точки зору того, звідки дісталися нинішні гроші. На щастя, його королівство дуже просте.

Царство можна змоделювати за допомогою 2D булевої матриці, де lпредставлений хтось, хто успадкував гроші, і Oпредставляє когось, хто цього не зробив. Наприклад:

l O l l

O O O l

l O l O

O O O l

(Це завжди буде прямокутник)

У наступному поколінні царство менше (Вовки сильні!).

Наступне покоління виглядатиме так, накладаючись на попереднє покоління ( xє заповнювачем для нащадка наступного покоління)

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

Нащадок буде дивитися на предків, які безпосередньо навколо них (Таким чином, в лівому верхньому кутку xбуде бачити { l, O, O, O}, називається Unaligned прямокутної околиці )

Якщо лише один предок успадкував гроші, нащадок успадкує гроші від них. Якщо більше одного предка успадкували гроші, вони будуть розбиратися, і нащадок у кінцевому підсумку не успадкує гроші. Якщо ніхто не успадкував гроші, нащадок не успадкує жодних грошей.

(Більше одного нащадка може успадкувати від одного предка)

Отже, наступне покоління виглядатиме так:

​
 l l O

 l l O

 l l O
​

Виклик

Вхідні дані

Поточний стан покоління, як масив масивів будь-яких двох різних значень, де внутрішні масиви мають однакову довжину.

Наприклад, для наведеного вище прикладу це може бути:

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

Вихідні дані

Ціле число, що представляє кількість унікальних попередніх поколінь, де вхідне наступне покоління.

Можна припустити, що відповідь завжди буде менше 2 ^ 30 - 1. (або 1073741823).

Попереднє покоління можна було б назвати "попереднім малюнком", і таким викликом було б підрахунок преім .

Оцінка балів

Це виклик, тому кожне подання буде тестоване на моєму комп’ютері, а подання, яке займає найменше часу, буде переможцем.

Приклад введення та виводу

(Де 1є нащадок, який успадкував гроші, і 0є нащадком, який не успадкував гроші)

Вхід:

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

Вихід:

4

Вхід:

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

Вихід:

254

Вхід:

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

Вихід:

11567

6
"lOOlLOOOOLLlololoLOLOLOOLOLOLOLLL" - це все, що я побачив, коли я вперше відкрив сторінку.
Чарівний восьминога Урна

Відповіді:


4

C ++ за допомогою бібліотеки BuDDy

Це здавалося приємним приводом грати з двійковими діаграмами рішення . Королівство перетворюється на велику булеву формулу, де нам належить порахувати кількість способів його задоволення. Це можна (іноді) зробити ефективніше, ніж це звучить.

Королівство повинно надаватися як програмна константа як плоский масив і чітко задані розміри. (Хороший вклад залишається читачем як:)

Ось бентежно простий код:

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

Для компіляції з debian 8 (jessie) встановіть libbdd-devі зробіть g++ -std=c++11 -o hist hist.cpp -lbdd. (Оптимізація параметрів майже не має значення, оскільки реальна робота виконується в бібліотеці.)

Великі приклади можуть призвести до повідомлень про збирання сміття. Їх можна було придушити, але я вважаю за краще їх бачити.

bdd_satcountповертає a double, але це досить добре для очікуваного діапазону результатів. Ця ж техніка підрахунку можлива з точними (великими) цілими числами.

Код оптимізовано для ROWS<COLS. Якщо у вас набагато більше рядків, ніж стовпці, можливо, було б корисно перенести матрицю.


2,39 секунди. Це вдвічі менший час! Позначення цього як прийнятого.
Художник

1
@Artyer: Чи хочете ви опублікувати свій найдовший прихований тестовий випадок? Як і ваше рішення, якщо хочете.
Ендрю Епштейн

@AndrewEpstein Нещодавно у мене був збій із жорсткого диска, і я втратив і код, і оригінальні тестові випадки (їх було сотні, і вони були макс. Вибачте.
Artyer

3

Python 2.7

Це просто наївна перша спроба. Це не особливо швидко, але це правильно.

Перше спостереження полягає в тому, що кожна клітина залежить від рівно чотирьох клітин у попередньому поколінні. Ми можемо представити ці чотири комірки як 4-розрядне число (0-15). Згідно з правилами, якщо точно одна сусідня клітина в попередньому поколінні 1, то дана клітина в поточному поколінні буде 1, інакше вона буде 0. Ті відповідають ступеням двох, а саме [1, 2, 4, 8]. Коли чотири предки представлені у вигляді 4-розрядного числа, будь-яке інше число призведе до появи 0у поточному поколінні. За допомогою цієї інформації, побачивши клітинку в поточному поколінні, ми можемо звузити можливості сусідства в попередньому поколінні до однієї з чотирьох або однієї з дванадцяти можливостей відповідно.

Я вирішив представити околиці так:

32
10

де 0 - найменш значущий біт тощо.

Друге спостереження полягає в тому, що для двох сусідніх комірок поточного покоління два мікрорайони попереднього покоління перекриваються:

32  32
10  10

або:

32
10

32
10

У горизонтальному випадку, 2лівий мікрорайон перекривається з сусіднім з 3правого, і аналогічно з 0лівим та 1праворуч. У вертикальному випадку 1околиці верху перекриваються з сусідніми з 3нижньої частини, а також аналогічно з 0верхньою та 2нижньою частиною.

Таке перекриття означає, що ми можемо звузити можливості для ще неприбраних мікрорайонів, виходячи з того, що ми вже обрали. Код працює так, як зліва направо, зверху вниз, в рекурсивному першому глибинному пошуку можливих зображень. Наступна діаграма вказує, які попередні мікрорайони ми повинні враховувати, переглядаючи можливі мікрорайони поточної комірки:

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]

Ось код:

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

Щоб запустити його:

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
Щоб приховати тестові справи, потрібно багато часу, тому я не забиваю цього подання. Спробуйте інший алгоритм, так як цей набір має занадто високу складність у часі (це O(<something>^n)я думаю.)
Artyer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.