this was a pain in the ass to write vs the initial implementation, but
it's a lot more efficient due to not revisiting nodes that are already
visited. i would warrant it's as efficient as possible at this point
also, both part1 and part2 use the generalized do-dfs form provided by
the graph package