22_cave.py 2.5 KB

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