税歴史家


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{表示されlOOO}と呼ばれる、アンアラインド矩形近傍

祖先が1人だけお金を受け継いだ場合、子孫は彼らからお金を受け継ぎます。複数の祖先がお金を相続した場合、彼らはつまずき、子孫は結局お金を相続しないことになります。誰もお金を相続していない場合、子孫はお金を相続しません。

(複数の子孫が1つの祖先から継承できます)

したがって、次世代は次のようになります。

​
 l l O

 l l O

 l l O
​

チャレンジ

入力

任意の2つの異なる値の配列の配列としての生成の現在の状態。内部配列はすべて同じ長さです。

たとえば、上記の例では、次のようになります。

[
  [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
最初にページを開いたとき、「lOOlLOOOOLLlololoLOLOLOOLOLOLOLL」だけが表示されていました。
マジックタコの壷

回答:


4

BuDDyライブラリを使用したC ++

これは、二分決定グラフを操作するいい言い訳のように思えました。王国は大きなブール式に変換され、それが満たされる方法の数を数える必要があります。それは(時々)思ったよりも効率的に行うことができます。

王国は、フラット配列としてプログラム定数として与えられ、明示的に次元が与えられなければなりません。(素敵な入力は読者のための言い訳として残されています:-)

恥ずかしいほど単純なコードを次に示します。

#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はを返しますがdouble、期待される結果範囲には十分です。正確な(大きな)整数でも同じカウント手法が可能です。

コードはに最適化されていROWS<COLSます。列よりも行数が多い場合は、行列を転置することをお勧めします。


2.39秒。これは私の半分の時間です!これを承認済みとしてマークします。
Artyer 2017年

1
@Artyer:最も長く実行されている非表示のテストケースを投稿しますか?必要に応じて、ソリューションと同様に。
Andrew Epstein

@AndrewEpstein最近ハードドライブに障害が発生し、コードと元のテストケースの両方が失われました(それらの数百があり、それらは最大300の幅、10の高さだったと思います)。ごめんなさい。
Artyer 2017

3

Python 2.7

これは単純な最初の試みです。特に速くはありませんが、正しいです。

最初の観察は、各セルが前の世代の正確に4つのセルに依存していることです。これらの4つのセルを4ビットの数値(0〜15)で表すことができます。ルールによれば、前の世代の隣接するセルが1つだけである場合1、現在の世代の特定のセルは1となり、それ以外の場合はになります0。それらは2の累乗、つまりに対応し[1, 2, 4, 8]ます。4つの祖先が4ビットの数値として表されている場合、他の数値は0現在の世代ではa になります。この情報を使用して、現在の世代のセルを確認すると、前の世代の近傍の可能性を、それぞれ4つまたは12つの可能性のうちの1つに絞り込むことができます。

私は次のように近所を表すことにしました:

32
10

ここで、0は最下位ビットなどです。

2番目の観察は、現在の世代の2つの隣接するセルについて、前の世代の2つの近傍が重複していることです。

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.