178 lines
5.2 KiB
Python
178 lines
5.2 KiB
Python
from collections import deque
|
|
from itertools import product
|
|
from tools.aoc import AOCDay
|
|
from typing import Any
|
|
|
|
|
|
class Valve:
|
|
def __init__(self, name: str, flowrate: int):
|
|
self.name: str = name
|
|
self.flowrate: int = flowrate
|
|
self.tunnels: set = set()
|
|
|
|
|
|
class Tunnel:
|
|
def __init__(self, target: Valve, length: int = 1):
|
|
self.target: Valve = target
|
|
self.length: int = length
|
|
|
|
def __str__(self):
|
|
return f"Tunnel(target={self.target.name}, length={self.length})"
|
|
|
|
def __repr__(self):
|
|
return str(self)
|
|
|
|
|
|
def get_openable_valve_tunnels(valve: Valve, open_valves: set, time_remaining: int) -> set:
|
|
tunnels = set()
|
|
queue = deque()
|
|
visited = set()
|
|
queue.append((0, valve))
|
|
while queue:
|
|
d, v = queue.popleft()
|
|
if v.name in visited:
|
|
continue
|
|
visited.add(v.name)
|
|
if v.name not in open_valves and d + 2 <= time_remaining:
|
|
tunnels.add((d, v))
|
|
for x in v.tunnels:
|
|
queue.append((d + x.length, x.target))
|
|
|
|
return tunnels
|
|
|
|
|
|
def get_max_flow(valve: Valve, open_valves: list, time_remaining: int = 30, depth: int = 0) -> int:
|
|
max_flow = 0
|
|
|
|
ov = {t for t in valve.tunnels if t.target not in open_valves and t.length < time_remaining - 2}
|
|
if time_remaining <= 0 or not ov:
|
|
return 0
|
|
|
|
for tunnel in ov:
|
|
this_open_flow = tunnel.target.flowrate * (time_remaining - tunnel.length - 1)
|
|
this_flow = get_max_flow(tunnel.target, open_valves + [tunnel.target], time_remaining - tunnel.length - 1, depth + 1)
|
|
if this_flow + this_open_flow > max_flow:
|
|
max_flow = this_flow + this_open_flow
|
|
|
|
|
|
return max_flow
|
|
|
|
|
|
def get_max_flow_double(valve1: Valve, valve2: Valve, open_valves: list, DP: dict, time_remaining_1: int = 26, time_remaining_2: int = 26, depth: int = 0) -> int:
|
|
dp_key = valve1.name + valve2.name + "%02d" % time_remaining_1 + "%02d" % time_remaining_2 + "".join(open_valves)
|
|
dp_key2 = valve2.name + valve1.name + "%02d" % time_remaining_1 + "%02d" % time_remaining_2 + "".join(open_valves)
|
|
if dp_key in DP:
|
|
return DP[dp_key]
|
|
|
|
if time_remaining_1 <= 0 and time_remaining_2 <= 0:
|
|
return 0
|
|
|
|
ov1 = {t for t in valve1.tunnels if t.target.name not in open_valves and t.length < time_remaining_1 - 2}
|
|
ov2 = {t for t in valve2.tunnels if t.target.name not in open_valves and t.length < time_remaining_2 - 2}
|
|
|
|
if not ov1 and not ov2:
|
|
return 0
|
|
|
|
if not ov1:
|
|
ov1 = {Tunnel(valve1, 99)}
|
|
|
|
if not ov2:
|
|
ov2 = {Tunnel(valve2, 99)}
|
|
|
|
permut = product(ov1, ov2)
|
|
|
|
max_flow = 0
|
|
for v1, v2 in permut:
|
|
if v1.target.name == v2.target.name:
|
|
continue
|
|
|
|
this_open_flow = 0
|
|
|
|
t1 = valve1
|
|
if v1.length + 2 <= time_remaining_1:
|
|
this_open_flow += v1.target.flowrate * (time_remaining_1 - v1.length - 1)
|
|
t1 = v1.target
|
|
|
|
t2 = valve2
|
|
if v2.length + 2 <= time_remaining_2:
|
|
this_open_flow += v2.target.flowrate * (time_remaining_2 - v2.length - 1)
|
|
t2 = v2.target
|
|
|
|
this_flow_rate = get_max_flow_double(
|
|
t1,
|
|
t2,
|
|
sorted(open_valves + [v1.target.name, v2.target.name]),
|
|
DP,
|
|
time_remaining_1 - v1.length - 1,
|
|
time_remaining_2 - v2.length - 1,
|
|
depth + 1
|
|
)
|
|
|
|
if this_flow_rate + this_open_flow > max_flow:
|
|
max_flow = this_flow_rate + this_open_flow
|
|
|
|
DP[dp_key] = max_flow
|
|
DP[dp_key2] = max_flow
|
|
return max_flow
|
|
|
|
|
|
class Day(AOCDay):
|
|
inputs = [
|
|
[
|
|
(1651, "input16_test"),
|
|
(1850, "input16"),
|
|
],
|
|
[
|
|
(1707, "input16_test"),
|
|
(2306, "input16"),
|
|
]
|
|
]
|
|
|
|
def get_valve_graph(self) -> Valve:
|
|
valves = {}
|
|
tmp_tunnels = {}
|
|
for line in self.getInput():
|
|
p = line.split(" ")
|
|
valve_name = p[1]
|
|
flowrate = int(p[4][5:-1])
|
|
tunnels = "".join(p[9:]).split(",")
|
|
valves[valve_name] = Valve(valve_name, flowrate)
|
|
tmp_tunnels[valve_name] = tunnels
|
|
|
|
for name, tunnels in tmp_tunnels.items():
|
|
valves[name].tunnels = {Tunnel(valves[t]) for t in tunnels}
|
|
|
|
for valve in valves.values():
|
|
tunnels = set()
|
|
queue = deque()
|
|
visited = set()
|
|
queue.append((0, valve))
|
|
while queue:
|
|
d, v = queue.popleft()
|
|
if v in visited:
|
|
continue
|
|
visited.add(v)
|
|
if v != valve and v.flowrate > 0:
|
|
tunnels.add(Tunnel(v, d))
|
|
for x in v.tunnels:
|
|
queue.append((d + 1, x.target))
|
|
|
|
tmp_tunnels[valve.name] = tunnels
|
|
|
|
for name, tunnels in tmp_tunnels.items():
|
|
valves[name].tunnels = tunnels
|
|
|
|
return valves["AA"]
|
|
|
|
def part1(self) -> Any:
|
|
return get_max_flow(self.get_valve_graph(), [])
|
|
|
|
def part2(self) -> Any:
|
|
root = self.get_valve_graph()
|
|
return get_max_flow_double(root, root, [], {})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
day = Day(2022, 16)
|
|
day.run(verbose=True)
|