export class MinHeap { private heap: T[] = []; private compare: (a: T, b: T) => number; constructor(compare: (a: T, b: T) => number) { this.compare = compare; } public get size(): number { return this.heap.length; } public add(elem: T) { this.heap.push(elem); this.heapifyUp(); } public pop(): T | undefined { if (this.heap.length === 0) { return undefined; } const ret = this.heap[0]; this.heap[0] = this.heap[this.heap.length - 1]; this.heap.pop(); this.heapifyDown(); return ret; } private heapifyUp() { let index = this.heap.length - 1; while (index > 0) { const parentIndex = Math.floor((index - 1) / 2); if (this.compare(this.heap[index], this.heap[parentIndex]) > 0) { break; } // swap const temp = this.heap[index]; this.heap[index] = this.heap[parentIndex]; this.heap[parentIndex] = temp; index = parentIndex; } } private heapifyDown() { let index = 0; while (index < this.heap.length) { const leftIndex = 2 * index + 1; const rightIndex = 2 * index + 2; let minIndex = leftIndex; if (leftIndex >= this.heap.length) { break; } if (rightIndex < this.heap.length && this.compare(this.heap[rightIndex], this.heap[minIndex]) < 0) { minIndex = rightIndex; } if (this.compare(this.heap[index], this.heap[minIndex]) < 0) { break; } // swap const temp = this.heap[index]; this.heap[index] = this.heap[minIndex]; this.heap[minIndex] = temp; index = minIndex; } } } type CostMap = number[][]; export type Coord = [number, number]; export type Dir = "left" | "right" | "up" | "down"; export type State = { pos: Coord, prevDir: Dir, dist: number, }; export function readCostMap(input: string): CostMap { const costMap = input.split("\n").map(x => x.trim()).filter(x => x.length > 0).map(x => x.split("").map(x => parseInt(x))); return costMap } function reflectDir(dir: Dir): Dir { switch (dir) { case "left": return "right"; case "right": return "left"; case "up": return "down"; case "down": return "up"; } } function nextCoord(pos: Coord, dir: Dir): Coord { switch (dir) { case "left": return [pos[0] - 1, pos[1]]; case "right": return [pos[0] + 1, pos[1]]; case "up": return [pos[0], pos[1] - 1]; case "down": return [pos[0], pos[1] + 1]; } } export function nextCrucibleStates(pos: Coord, prevDir: Dir, step: number): State[] { const availableDirs = ["left", "right", "up", "down"] satisfies Dir[]; return availableDirs.filter(dir => { return dir !== reflectDir(prevDir) && ( dir !== prevDir || step < 3 ); }).map(dir => ({ pos: nextCoord(pos, dir), prevDir: dir, dist: dir === prevDir ? step + 1 : 1, })); } export function nextUltraCrucibleStates(pos: Coord, prevDir: Dir, step: number): State[] { const availableDirs = ["left", "right", "up", "down"] satisfies Dir[]; return availableDirs.filter(dir => { return dir !== reflectDir(prevDir) && ( dir !== prevDir || step < 10 ) && ( dir === prevDir || step > 3 ); }).map(dir => ({ pos: nextCoord(pos, dir), prevDir: dir, dist: dir === prevDir ? step + 1 : 1, })); } export function solve(costMap: CostMap, finalPos: Coord): number { const [height, width] = [costMap.length, costMap[0].length]; const minHeap = new MinHeap<[State, number]>((a, b) => a[1] - b[1]); minHeap.add([{ pos: [0, 0], prevDir: "down", dist: 0 }, 0]); const minCost = new Map(); while (minHeap.size > 0) { const [state, cost] = minHeap.pop()!; const curMinCost = minCost.get(`${state.pos.join(",")};${state.prevDir},${state.dist}`); if (curMinCost !== undefined && curMinCost < cost) { continue; } const nextAvailableStates = nextCrucibleStates(state.pos, state.prevDir, state.dist) .filter(x => x.pos[0] >= 0 && x.pos[0] < width && x.pos[1] >= 0 && x.pos[1] < height); for (const nextState of nextAvailableStates) { const key = `${nextState.pos.join(",")};${nextState.prevDir},${nextState.dist}`; const nextCost = cost + costMap[nextState.pos[1]][nextState.pos[0]]; const curMinCost = minCost.get(key); if (curMinCost === undefined || curMinCost > nextCost) { minCost.set(key, nextCost); minHeap.add([nextState, nextCost]); } } } let ret = Number.MAX_SAFE_INTEGER; for (const [key, value] of minCost) { if (key.split(";")[0] === finalPos.join(",")) { ret = Math.min(ret, value); } } return ret; } export function solve_2(costMap: CostMap, finalPos: Coord): number { const [height, width] = [costMap.length, costMap[0].length]; const minHeap = new MinHeap<[State, number]>((a, b) => a[1] - b[1]); minHeap.add([{ pos: [0, 0], prevDir: "down", dist: 0 }, 0]); minHeap.add([{ pos: [0, 0], prevDir: "right", dist: 0 }, 0]); const minCost = new Map(); while (minHeap.size > 0) { const [state, cost] = minHeap.pop()!; const curMinCost = minCost.get(`${state.pos.join(",")};${state.prevDir},${state.dist}`); if (curMinCost !== undefined && curMinCost < cost) { continue; } const nextAvailableStates = nextUltraCrucibleStates(state.pos, state.prevDir, state.dist) .filter(x => x.pos[0] >= 0 && x.pos[0] < width && x.pos[1] >= 0 && x.pos[1] < height); for (const nextState of nextAvailableStates) { const key = `${nextState.pos.join(",")};${nextState.prevDir},${nextState.dist}`; const nextCost = cost + costMap[nextState.pos[1]][nextState.pos[0]]; const curMinCost = minCost.get(key); if (curMinCost === undefined || curMinCost > nextCost) { minCost.set(key, nextCost); minHeap.add([nextState, nextCost]); } } } // console.log([...minCost.entries()]); let ret = Number.MAX_SAFE_INTEGER; for (const [key, value] of minCost) { if (key.split(";")[0] === finalPos.join(",") && parseInt(key.split(";")[1].split(",")[1]) >= 4) { ret = Math.min(ret, value); } } return ret; }