22_cave.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. #!/usr/bin/env python3
  2. depth = 6084
  3. tx, ty = target = 14, 709
  4. def scan(pad):
  5. def erode(geo_index):
  6. return (geo_index + depth) % 20183
  7. w = tx + pad + 1
  8. h = ty + pad + 1
  9. grid = [erode(x * 16807) for x in range(w)]
  10. for y in range(1, h):
  11. grid.append(erode(y * 48271))
  12. for x in range(1, w):
  13. if x == tx and y == ty:
  14. idx = 0
  15. else:
  16. top = grid[(y - 1) * w + x]
  17. left = grid[y * w + x - 1]
  18. idx = top * left
  19. grid.append(erode(idx))
  20. for i in range(len(grid)):
  21. grid[i] %= 3
  22. return grid, w
  23. def shortest_path(graph, source, target):
  24. Q = set(i for i, nb in enumerate(graph) if nb)
  25. inf = 1 << 32
  26. dist = len(graph) * [inf]
  27. dist[source] = 0
  28. while Q:
  29. u = min(Q, key=dist.__getitem__)
  30. if u == target:
  31. return dist[u]
  32. Q.remove(u)
  33. for v, weight in graph[u]:
  34. if v in Q:
  35. alt = dist[u] + weight
  36. if alt < dist[v]:
  37. dist[v] = alt
  38. def rescue(grid, w):
  39. # approach:
  40. # - build graph with (x, y, tool) tuples as vertices
  41. # - record weighted edges including tolao witching between vertices where
  42. # switching is allowed
  43. # - do Dijkstra on the result
  44. h = len(grid) // w
  45. def neighbours(i):
  46. y, x = divmod(i, w)
  47. if y > 0: yield i - w
  48. if x > 0: yield i - 1
  49. if x < w - 1: yield i + 1
  50. if y < h - 1: yield i + w
  51. TORCH, GEAR, NEITHER = range(3)
  52. tools = [
  53. (GEAR, TORCH), # rocky
  54. (GEAR, NEITHER), # wet
  55. (TORCH, NEITHER), # narrow
  56. ]
  57. # for efficiency, graph is encoded as 3 concatenated lists of (y * w + x)
  58. # vertices, one for each tool
  59. off = len(grid)
  60. graph = [[] for i in range(3 * off)]
  61. for i, sty in enumerate(grid):
  62. for j in neighbours(i):
  63. tty = grid[j]
  64. for stool in tools[sty]:
  65. for ttool in tools[tty]:
  66. if ttool in tools[sty]:
  67. cost = 1 if ttool == stool else 8
  68. graph[i + off * stool].append((j + off * ttool, cost))
  69. # we start and end with a torch so in the first of 3 lists
  70. return shortest_path(graph, 0, ty * w + tx)
  71. grid, w = scan(15)
  72. # part 1
  73. print(sum(sum(grid[y * w:y * w + tx + 1]) for y in range(ty + 1)))
  74. # part 2
  75. print(rescue(grid, w))