<p>There are <code>8</code> prison cells in a row and each cell is either occupied or vacant.</p>

<p>Each day, whether the cell is occupied or vacant changes according to the following rules:</p>

<ul>
	<li>If a cell has two adjacent neighbors that are both occupied or both vacant, then the cell becomes occupied.</li>
	<li>Otherwise, it becomes vacant.</li>
</ul>

<p><strong>Note</strong> that because the prison is a row, the first and the last cells in the row can&#39;t have two adjacent neighbors.</p>

<p>You are given an integer array <code>cells</code> where <code>cells[i] == 1</code> if the <code>i<sup>th</sup></code> cell is occupied and <code>cells[i] == 0</code> if the <code>i<sup>th</sup></code> cell is vacant, and you are given an integer <code>n</code>.</p>

<p>Return the state of the prison after <code>n</code> days (i.e., <code>n</code> such changes described above).</p>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>

<pre>
<strong>Input:</strong> cells = [0,1,0,1,1,0,0,1], n = 7
<strong>Output:</strong> [0,0,1,1,0,0,0,0]
<strong>Explanation:</strong> The following table summarizes the state of the prison on each day:
Day 0: [0, 1, 0, 1, 1, 0, 0, 1]
Day 1: [0, 1, 1, 0, 0, 0, 0, 0]
Day 2: [0, 0, 0, 0, 1, 1, 1, 0]
Day 3: [0, 1, 1, 0, 0, 1, 0, 0]
Day 4: [0, 0, 0, 0, 0, 1, 0, 0]
Day 5: [0, 1, 1, 1, 0, 1, 0, 0]
Day 6: [0, 0, 1, 0, 1, 1, 0, 0]
Day 7: [0, 0, 1, 1, 0, 0, 0, 0]
</pre>

<p><strong class="example">Example 2:</strong></p>

<pre>
<strong>Input:</strong> cells = [1,0,0,1,0,0,1,0], n = 1000000000
<strong>Output:</strong> [0,0,1,1,1,1,1,0]
</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>cells.length == 8</code></li>
	<li><code>cells[i]</code>&nbsp;is either <code>0</code> or <code>1</code>.</li>
	<li><code>1 &lt;= n &lt;= 10<sup>9</sup></code></li>
</ul>

class Solution:
    def prisonAfterNDays(self, cells: list[int], n: int) -> list[int]:
        self.testSimulate()
        return self.simulate(cells, n)

    def simulate(self, cells: list[int], days: int) -> list[int]:
        """Computes the state of the cells after a given number of days."""
        seen = {}
        day = 0
        key = self.hash_cells(cells)
        seen[key] = 0

        while day < days:
            cells = self.step(cells)
            day += 1
            key = self.hash_cells(cells)
            if key in seen:
                break
            seen[key] = day
        if day == days:
            return cells

        cycle_start = seen[key]
        cycle_len = day - cycle_start
        iters = (days - cycle_start) // cycle_len
        last_start = cycle_start + cycle_len * iters

        for d in range(days - last_start):
            cells = self.step(cells)
        return cells

    def step(self, cells: list[int]) -> list[int]:
        """Advances the state of the cells by one day."""
        next = [0] * len(cells)
        for c in range(1, len(cells) - 1):
            if cells[c - 1] == cells[c + 1]:
                next[c] = 1
        return next

    def hash_cells(self, cells: list[int]) -> int:
        """Computes a numeric hash for a cell state."""
        key = 0
        for cell in cells:
            key = key << 1
            key += cell
        return key

    def testSimulate(self):
        assert self.simulate([0, 1, 1, 0], 1) == [0, 0, 0, 0]
        assert self.simulate([0, 1, 1, 0], 2) == [0, 1, 1, 0]
        assert self.simulate([0, 1, 1, 0], 3) == [0, 0, 0, 0]
        assert self.simulate([0, 1, 1, 0], 999) == [0, 0, 0, 0]
        assert self.simulate([0, 1, 1, 0], 1000) == [0, 1, 1, 0]
16300