You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

78 lines
2.3KB

  1. from functools import lru_cache
  2. @lru_cache(maxsize=None)
  3. def find_min_dist(valid, a, b, DX=[1, -1, 1j, -1j]):
  4. dist = 0
  5. state = {a}
  6. seen = state.copy()
  7. while state:
  8. dist += 1
  9. state = {x + dx for x in state for dx in DX} & valid - seen
  10. seen.update(state)
  11. if b in state:
  12. return dist
  13. def evolve(old, pinned):
  14. valid = frozenset({p for p, c in old if c == '.'})
  15. for x, k in old:
  16. if x not in pinned and k.isalpha():
  17. xi = 'ABCD'.index(k) * 2 + 3
  18. cost = 10 ** 'ABCD'.index(k)
  19. # rule: if you're in the hall, you can only end up in your room
  20. if x.imag == 1:
  21. ys = {complex(xi, y) for y in slots}
  22. # rule: if you're in a wrong spot, you can only end up in the hall
  23. else:
  24. ys = {complex(x, 1) for x in (1, 2, 4, 6, 8, 10, 11)}
  25. for y in ys & valid:
  26. # heuristic: assume only A can waste time with alcove
  27. if y.real in {1, 2} and k != 'A':
  28. continue
  29. # rule: you only get one move
  30. new_pinned = pinned
  31. if y.imag > 1:
  32. new_pinned |= {y}
  33. if dist := find_min_dist(valid, x, y):
  34. new = old - {(x, k), (y, '.')} | {(x, '.'), (y, k)}
  35. options = {scores.get(new), scores[old] + cost * dist}
  36. scores[new] = min(options - {None})
  37. yield new, new_pinned
  38. text = open(0).read()
  39. expansion = '''
  40. #D#C#B#A#
  41. #D#B#A#C#
  42. '''
  43. expanded_text = text.replace('#\n ', '#' + expansion + ' ', 1)
  44. for text in [text, expanded_text]:
  45. slots = list(range(2, len(text.splitlines()) - 1))
  46. init = frozenset(
  47. (complex(x, y), ch)
  48. for y, ln in enumerate(text.splitlines())
  49. for x, ch in enumerate(ln)
  50. )
  51. goal = {
  52. (complex(i * 2 + 3, y), ch)
  53. for i, ch in enumerate('ABCD')
  54. for y in slots
  55. }
  56. scores = {init: 0}
  57. states = {(init, frozenset())}
  58. wins = set()
  59. seen = set()
  60. i = 0
  61. while states:
  62. i += 1; print(i, len(states))
  63. states = {new for old in states for new in evolve(*old)} - seen
  64. seen |= states
  65. if wins := {old[0] for old in states if goal < old[0]}:
  66. print('end', scores[min(wins, key=scores.get)])
  67. break