|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
- 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
|