|
|
@@ -0,0 +1,77 @@ |
|
|
|
from functools import lru_cache |
|
|
|
|
|
|
|
|
|
|
|
@lru_cache(maxsize=None) |
|
|
|
def find_min_dist(valid, a, b, DX=[1, -1, 1j, -1j]): |
|
|
|
dist = 0 |
|
|
|
state = {a} |
|
|
|
seen = state.copy() |
|
|
|
while state: |
|
|
|
dist += 1 |
|
|
|
state = {x + dx for x in state for dx in DX} & valid - seen |
|
|
|
seen.update(state) |
|
|
|
if b in state: |
|
|
|
return dist |
|
|
|
|
|
|
|
|
|
|
|
def evolve(old, pinned): |
|
|
|
valid = frozenset({p for p, c in old if c == '.'}) |
|
|
|
for x, k in old: |
|
|
|
if x not in pinned and k.isalpha(): |
|
|
|
xi = 'ABCD'.index(k) * 2 + 3 |
|
|
|
cost = 10 ** 'ABCD'.index(k) |
|
|
|
|
|
|
|
# rule: if you're in the hall, you can only end up in your room |
|
|
|
if x.imag == 1: |
|
|
|
ys = {complex(xi, y) for y in slots} |
|
|
|
# rule: if you're in a wrong spot, you can only end up in the hall |
|
|
|
else: |
|
|
|
ys = {complex(x, 1) for x in (1, 2, 4, 6, 8, 10, 11)} |
|
|
|
|
|
|
|
for y in ys & valid: |
|
|
|
# heuristic: assume only A can waste time with alcove |
|
|
|
if y.real in {1, 2} and k != 'A': |
|
|
|
continue |
|
|
|
|
|
|
|
# rule: you only get one move |
|
|
|
new_pinned = pinned |
|
|
|
if y.imag > 1: |
|
|
|
new_pinned |= {y} |
|
|
|
|
|
|
|
if dist := find_min_dist(valid, x, y): |
|
|
|
new = old - {(x, k), (y, '.')} | {(x, '.'), (y, k)} |
|
|
|
options = {scores.get(new), scores[old] + cost * dist} |
|
|
|
scores[new] = min(options - {None}) |
|
|
|
yield new, new_pinned |
|
|
|
|
|
|
|
|
|
|
|
text = open(0).read() |
|
|
|
expansion = ''' |
|
|
|
#D#C#B#A# |
|
|
|
#D#B#A#C# |
|
|
|
''' |
|
|
|
expanded_text = text.replace('#\n ', '#' + expansion + ' ', 1) |
|
|
|
for text in [text, expanded_text]: |
|
|
|
slots = list(range(2, len(text.splitlines()) - 1)) |
|
|
|
init = frozenset( |
|
|
|
(complex(x, y), ch) |
|
|
|
for y, ln in enumerate(text.splitlines()) |
|
|
|
for x, ch in enumerate(ln) |
|
|
|
) |
|
|
|
goal = { |
|
|
|
(complex(i * 2 + 3, y), ch) |
|
|
|
for i, ch in enumerate('ABCD') |
|
|
|
for y in slots |
|
|
|
} |
|
|
|
scores = {init: 0} |
|
|
|
states = {(init, frozenset())} |
|
|
|
wins = set() |
|
|
|
seen = set() |
|
|
|
i = 0 |
|
|
|
while states: |
|
|
|
i += 1; print(i, len(states)) |
|
|
|
states = {new for old in states for new in evolve(*old)} - seen |
|
|
|
seen |= states |
|
|
|
if wins := {old[0] for old in states if goal < old[0]}: |
|
|
|
print('end', scores[min(wins, key=scores.get)]) |
|
|
|
break |