package main import ( "fmt" "regexp" "strconv" "strings" h "git.bullercodeworks.com/brian/adventofcode/helpers" ) var valveregexp = regexp.MustCompile(`([A-Z]{2}).*=(\d+);.*?((?:[A-Z]{2}(?:, )?)+)`) func main() { rooms := parseRooms(h.StdinToStringSlice()) graph := floydWarshall(rooms) v := NewVolcano(rooms, graph) r := part1(v) v = NewVolcano(rooms, graph) part2(v, r) } func part1(v *Volcano) uint16 { var dfs func(target, pressure, minute, on, node uint16) uint16 dfs = func(target, pressure, minute, on, node uint16) uint16 { max := pressure for _, w := range v.goodbits { if node == w[0] || w[0] == v.start || w[0]&on != 0 { continue } l := v.bitgraphs[node|w[0]] + 1 if minute+l > target { continue } if next := dfs(target, pressure+(target-minute-l)*w[1], minute+l, on|w[0], w[0]); next > max { max = next } } return max } res := dfs(30, 0, 0, 0, v.start) fmt.Println("# Part 1") fmt.Println(res) return res } func part2(v *Volcano, p1 uint16) { var dfspaths func(target, pressure, minute, on, node, path uint16) [][2]uint16 dfspaths = func(target, pressure, minute, on, node, path uint16) [][2]uint16 { paths := [][2]uint16{{pressure, path}} for _, w := range v.goodbits { if w[0] == node || w[0] == v.start || w[0]&on != 0 { continue } l := v.bitgraphs[node|w[0]] + 1 if minute+l > target { continue } paths = append(paths, dfspaths(target, pressure+(target-minute-l)*w[1], minute+l, on|w[0], w[0], path|w[0])...) } return paths } allpaths := dfspaths(26, 0, 0, 0, v.start, 0) // reduce paths (presumably, both paths are at least half of part 1) var trimpaths [][2]uint16 for _, p := range allpaths { if p[0] > p1/2 { trimpaths = append(trimpaths, p) } } // compare all paths to find max var max uint16 = 0 for idx := 0; idx < len(trimpaths); idx += 1 { for jdx := idx + 1; jdx < len(trimpaths); jdx += 1 { if trimpaths[idx][1]&trimpaths[jdx][1] != 0 { continue } if m := trimpaths[idx][0] + trimpaths[jdx][0]; m > max { max = m } } } fmt.Println("# Part 2") fmt.Println(max) } type Volcano struct { start uint16 goodrooms []*Room goodbits [][2]uint16 bitfield map[*Room]uint16 bitgraphs []uint16 } func NewVolcano(rooms []*Room, graph map[*Room]map[*Room]uint16) *Volcano { v := Volcano{} // pick valves with flow and starting point for _, r := range rooms { if r.Rate > 0 || r.Name == "AA" { v.goodrooms = append(v.goodrooms, r) } } // assign bits v.bitfield = make(map[*Room]uint16) for idx, r := range v.goodrooms { v.bitfield[r] = 1 << idx } // find start for _, r := range v.goodrooms { if r.Name == "AA" { v.start = v.bitfield[r] break } } // create slice for fast edge lookup v.bitgraphs = make([]uint16, 0xffff) for _, v1 := range v.goodrooms { for _, v2 := range v.goodrooms { v.bitgraphs[v.bitfield[v1]|v.bitfield[v2]] = graph[v1][v2] } } // create slice for fast node lookup v.goodbits = make([][2]uint16, len(v.goodrooms)) for idx, r := range v.goodrooms { v.goodbits[idx] = [2]uint16{v.bitfield[r], r.Rate} } return &v } type Room struct { Name string Rate uint16 Edges string } func parseRooms(inp []string) []*Room { rooms := make([]*Room, len(inp)) for idx, line := range inp { m := valveregexp.FindStringSubmatch(line) i, _ := strconv.Atoi(m[2]) rooms[idx] = &Room{Name: m[1], Rate: uint16(i), Edges: m[3]} } return rooms } func floydWarshall(valves []*Room) map[*Room]map[*Room]uint16 { graph := make(map[*Room]map[*Room]uint16) for _, v1 := range valves { graph[v1] = make(map[*Room]uint16) for _, v2 := range valves { if v1 == v2 { graph[v1][v2] = 0 } else if strings.Contains(v1.Edges, v2.Name) { graph[v1][v2] = 1 } else { graph[v1][v2] = 0xff } } } for _, k := range valves { for _, i := range valves { for _, j := range valves { if graph[i][j] > graph[i][k]+graph[k][j] { graph[i][j] = graph[i][k] + graph[k][j] } } } } return graph }