123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- import sequtils, strutils, tables
-
- type
- Point = tuple[x,y: int]
- Tool = enum tTorch, tClimbing, tNeither
- Node = tuple[pos: Point, tool: Tool]
-
- iterator moves(point: Point): Point =
- let (x, y) = point
-
- if y > 0: yield (x, y - 1)
- if x > 0: yield (x - 1, y)
- yield (x, y + 1)
- yield (x + 1, y)
-
- let
- input = readFile("data/22.txt").splitLines
- depth = input[0].strip.split(' ')[1].parseInt
- targetParts = input[1].strip.split(' ')[1].split(',').map(parseInt)
- target: Point = (targetParts[0], targetParts[1])
- targetNode: Node = (target, tTorch)
- tools = [[tClimbing, tTorch], [tClimbing, tNeither], [tTorch, tNeither]]
-
- var
- erosions = initTable[Point, int]()
- dangerSum = 0
-
- # Add arbitrary extension to allow for a bit of overshooting.
- for y in 0..target.y + 75:
- for x in 0..target.x + 75:
- let
- geoindex = if (x == 0 and y == 0) or (x, y) == target:
- 0
- elif y == 0:
- x * 16807
- elif x == 0:
- y * 48271
- else:
- erosions[(x-1, y)] * erosions[(x, y-1)]
- erosionlevel = (geoindex + depth) mod 20183
-
- erosions[(x, y)] = erosionlevel
- if x <= target.x and y <= target.y:
- dangerSum += erosionlevel mod 3
-
- # Custom stack of pending steps, kept in order from smallest to largest.
- # Insertion is O(n), removing the smallest is O(1). Performs approximately a
- # billionty times faster than I managed using a Seq/Deque/Table/CountingTable.
- type
- StackStep = ref object
- node: Node
- distance: int
- next: StackStep
-
- Stack = object
- head: StackStep
-
- proc insert(stack: var Stack, node: Node, distance: int) =
- var newNode = new(StackStep)
- newNode.node = node
- newNode.distance = distance
-
- if stack.head == nil:
- stack.head = newNode
- else:
- var target = stack.head
- while target.next != nil and target.next.distance < distance:
- target = target.next
- newNode.next = target.next
- target.next = newNode
-
- proc popSmallest(stack: var Stack): tuple[node: Node, distance: int] =
- result = (stack.head.node, stack.head.distance)
- stack.head = stack.head.next
-
- var
- distances = initTable[Node, int]()
- stack: Stack
-
- stack.insert(((0, 0), tTorch), 0)
-
- while not distances.hasKey(targetNode):
- let (node, distance) = stack.popSmallest
-
- # We can have duplicate steps in our stack, but if we've already
- # put the distance then that route was necessarily shorter. Just
- # skip it.
- if distances.hasKey(node):
- continue
-
- distances[node] = distance
-
- # At each node we can switch tools once, with a cost of 7 minutes
- for tool in tools[erosions[node.pos] mod 3]:
- if tool != node.tool and not distances.hasKey((node.pos, tool)):
- stack.insert(((node.pos, tool)), distance + 7)
-
- # Up to four possible moves from the current node, depending on
- # terrain and tools.
- for newPos in node.pos.moves:
- if erosions.hasKey(newPos) and
- node.tool in tools[erosions[newPos] mod 3] and
- not distances.hasKey((newPos, node.tool)):
- stack.insert(((newPos, node.tool)), distance + 1)
-
- echo dangerSum
- echo distances[targetNode]
|