Day 16

import Solution from "./solution.ts";

const regEx = /Valve (\w{2}).*rate=(-?\d+);.*valves? (.*)/;

function findShortestPaths(
  valves: Map<string, Valve>
): [Valve, Map<string, Valve>] {
  const nodes = new Set([...valves.values()]);
  for (const k of nodes) {
    for (const i of nodes) {
      for (const j of nodes) {
        const dist =
          valves.get(i.name)!.pathTo(k.name) +
          valves.get(k.name)!.pathTo(j.name);
        if (dist < valves.get(i.name)!.pathTo(j.name)) {
          valves.get(i.name)!.setPath(j.name, dist);
          valves.get(j.name)!.setPath(i.name, dist);
        }
      }
    }
  }
  const start = valves.get("AA")!;
  for (const key of valves.keys()) {
    if (valves.get(key)!.value === 0) {
      valves.delete(key);
    }
  }
  return [start, valves];
}

class Cache {
  #map = new Map<string, number>();

  #key(time: number, visited: Set<string>) {
    return [time, ...visited].join(",");
  }

  set(time: number, visited: Set<string>, value: number) {
    const key = this.#key(time, visited);
    this.#map.set(key, Math.max(value, this.get(time, visited)));
    return this;
  }

  get(time: number, visited: Set<string>) {
    const key = this.#key(time, visited);
    return this.#map.get(key) ?? 0;
  }

  team() {
    const arr = [...this.#map].sort(([, v1], [, v2]) => v2 - v1);
    let best = 0;
    for (let i = 0; i < arr.length; i++) {
      for (let j = i + 1; j < arr.length; j++) {
        if (!doSetIntersect(arr[i][0], arr[j][0])) {
          best = Math.max(best, arr[i][1] + arr[j][1]);
          break;
        }
      }
      if (arr[i][1] < best / 2) {
        break;
      }
    }
    return best;
  }
}

function dfs2(start: Valve, time: number, matrix: Map<string, Valve>): number {
  const cache = new Cache();
  function calc(from: Valve, minutes: number, visited: Set<string>): void {
    for (const [name, valve] of matrix) {
      if (!visited.has(name)) {
        const future = from.pathTo(name) + minutes + 1;
        if (future >= time) {
          cache.set(time - 1, visited, cache.get(minutes, visited));
        } else {
          const newVisited = new Set(visited).add(name);
          const newValue = valve.value * (time - future);
          cache.set(future, newVisited, newValue + cache.get(minutes, visited));
          if (visited.size === matrix.size) {
            cache.set(time - 1, visited, cache.get(future, newVisited));
          } else {
            calc(valve, future, newVisited);
          }
        }
      }
    }
  }
  calc(start, 0, new Set());
  return cache.team();
}

function takeOne(matrix: Map<string, Valve>): [Valve, Map<string, Valve>][] {
  return [...matrix].map(([_, start], i, all) => [
    start,
    new Map([...all].filter((_, x) => i !== x)),
  ]);
}

function dfs1(start: Valve, time: number, matrix: Map<string, Valve>): number {
  const cache = new Map<string, number>();
  function calc(
    start: Valve,
    minutes: number,
    matrix: Map<string, Valve>
  ): number {
    const key = [start.name, minutes, [...matrix.keys()].join("_")].join(",");
    if (cache.has(key)) return cache.get(key)!;
    if (minutes === 0) {
      return 0;
    }
    const val = start.value * (minutes - 1);
    const newValue = Math.max(
      val,
      ...takeOne(matrix)
        .filter(([v]) => start.pathTo(v.name) < minutes)
        .map(([v, m]) => val + calc(v, minutes - start.pathTo(v.name) - 1, m))
    );
    cache.set(key, newValue);
    return newValue;
  }
  return calc(start, time + 1, matrix);
}

function doSetIntersect(a: string, b: string) {
  const s = a.split(",").slice(1);
  for (const v of s) {
    if (b.includes(v)) return true;
  }
  return false;
}

class Valve {
  #name: string;
  #value: number;
  #leadsTo: Map<string, number>;

  constructor(line: string) {
    const [_, name, value, valves] = regEx.exec(line)!;
    this.#name = name;
    this.#value = Number.parseInt(value);
    this.#leadsTo = new Map(valves.split(", ").map((d) => [d, 1]));
    this.#leadsTo.set(name, 0);
  }

  pathTo(ident: string): number {
    return this.#leadsTo.get(ident) ?? Number.MAX_SAFE_INTEGER;
  }

  setPath(ident: string, length: number) {
    return this.#leadsTo.set(ident, length);
  }

  get name() {
    return this.#name;
  }

  get value() {
    return this.#value;
  }

  [Symbol.for("Deno.customInspect")]() {
    return Deno.inspect(this.#leadsTo);
  }
}

const task = new Solution(
  (entries: Valve[]) => {
    const [start, valves] = findShortestPaths(
      new Map(entries.map((v) => [v.name, v]))
    );
    return dfs1(start, 30, valves);
  },
  (entries: Valve[]) => {
    const [start, valves] = findShortestPaths(
      new Map(entries.map((v) => [v.name, v]))
    );
    return dfs2(start, 26, valves);
  },
  {
    transform: (a) => new Valve(a),
    sep: "\n",
  }
);
task.expect(1651, 1707);

export default task;

Last edited 04. April 2025 13:29