199 lines
6.7 KiB
TypeScript
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;
|
|
} |