aoc-2023/day_17/solver.ts
2024-12-09 22:41:02 +09:00

199 lines
6.7 KiB
TypeScript

export class MinHeap<T> {
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<string, number>();
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<string, number>();
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;
}