#!/usr/bin/env python3 from heapq import heapify, heappush, heappop depth = 6084 tx, ty = target = 14, 709 #depth = 510 #tx, ty = target = 10, 10 def scan(pad): def erode(geo_index): return (geo_index + depth) % 20183 w = tx + pad + 1 h = ty + pad + 1 grid = [erode(x * 16807) for x in range(w)] for y in range(1, h): grid.append(erode(y * 48271)) for x in range(1, w): if x == tx and y == ty: idx = 0 else: top = grid[(y - 1) * w + x] left = grid[y * w + x - 1] idx = top * left grid.append(erode(idx)) for i in range(len(grid)): grid[i] %= 3 return grid, w def shortest_path(graph, source, target): dist = len(graph) * [1 << 32] dist[source] = 0 Q = [(dist[v], v) for v, nb in enumerate(graph) if nb] heapify(Q) while Q: udist, u = heappop(Q) if udist == dist[u]: if u == target: return udist for v, weight in graph[u]: alt = udist + weight if alt < dist[v]: dist[v] = alt heappush(Q, (alt, v)) def rescue(grid, w): # approach: # - build graph with (x, y, tool) tuples as vertices # - record weighted edges including tolao witching between vertices where # switching is allowed # - do Dijkstra on the result h = len(grid) // w def neighbours(i): y, x = divmod(i, w) if y > 0: yield i - w if x > 0: yield i - 1 if x < w - 1: yield i + 1 if y < h - 1: yield i + w TORCH, GEAR, NEITHER = range(3) tools = [ (GEAR, TORCH), # rocky (GEAR, NEITHER), # wet (TORCH, NEITHER), # narrow ] # for efficiency, graph is encoded as 3 concatenated lists of (y * w + x) # vertices, one for each tool off = len(grid) graph = [[] for i in range(3 * off)] for i, sty in enumerate(grid): for j in neighbours(i): tty = grid[j] for stool in tools[sty]: for ttool in tools[tty]: if ttool in tools[sty]: cost = 1 if ttool == stool else 8 graph[i + off * stool].append((j + off * ttool, cost)) # we start and end with a torch so in the first of 3 lists return shortest_path(graph, 0, ty * w + tx) grid, w = scan(15) # part 1 print(sum(sum(grid[y * w:y * w + tx + 1]) for y in range(ty + 1))) # part 2 print(rescue(grid, w))