package day10 import ( "cmp" "fmt" "iter" "math" "git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc" "git.bizdoc.ro/private/devkit.git/collections/combinatorics/v2" ) func Part2V3(ctx aoc.Context) (out int, err error) { // https://www.reddit.com/r/adventofcode/comments/1pk87hl/2025_day_10_part_2_bifurcate_your_way_to_victory/ inputLines, err := ParseInput(ctx) if err != nil { return 0, fmt.Errorf("parse input: %w", err) } for _, line := range inputLines { parityMap := make(map[uint64][]ButtonSet) for p := range generateButtonsCombinations(line.Buttons, len(line.Joltages)) { phash := ParityHash(p.Parity) parityMap[phash] = append(parityMap[phash], p) } var solve func([]int) (int, error) solve = func(joltage []int) (int, error) { if isFullOf(joltage, 0) { return 0, nil } h := HashOfParityOf(joltage) candidates, ok := parityMap[h] if !ok { return 0, fmt.Errorf("no candidates for parity") } best := math.MaxInt for _, candidate := range candidates { next := SubtractAndHalf(joltage, candidate.Joltages) if !isAllGraterThan(next, 0) { continue } if cost, err := solve(next); err == nil { cost = cost*2 + candidate.ButtonsPresses if cost < best { best = cost } } } if best == math.MaxInt { return 0, fmt.Errorf("no valid solution") } return best, nil } got, err := solve(line.Joltages) if err != nil { panic(err) } out += got } return } func isFullOf[T ~[]E, E comparable](s T, t E) bool { for _, v := range s { if v != t { return false } } return true } func isAllLessThan[T ~[]E, E cmp.Ordered](s T, t E) bool { for _, e := range s { if e >= t { return false } } return true } func isAllGraterThan[T ~[]E, E cmp.Ordered](s T, t E) bool { for _, e := range s { if e < t { return false } } return true } type ButtonSet struct { ButtonsPresses int Joltages []int Parity []int } func SubtractAndHalf(j1, j2 []int) []int { if len(j1) != len(j2) { panic("j1 != j2") } j3 := make([]int, len(j1)) for i := range j3 { j3[i] = j1[i] - j2[i] if j3[i]&1 != 0 { panic("j3[i] is not even") } j3[i] /= 2 } return j3 } func HashOfParityOf(jotages []int) uint64 { parity := ParityOf(jotages) return ParityHash(parity) } func ParityHash(parity []int) uint64 { var k uint64 for i, isTrue := range parity { if i > 60 { panic("parity out of range") } mask := uint64(isTrue) << (len(parity) - i - 1) k |= mask } return k } func DebugParityHashString(parity []int) string { return fmt.Sprintf("%0*b", len(parity), ParityHash(parity)) } func ParityOf(joltages []int) []int { parity := make([]int, len(joltages)) for i := range joltages { parity[i] = joltages[i] & 1 } return parity } func generateButtonsCombinations(s [][]int, joltageLen int) iter.Seq[ButtonSet] { buttons := make([]int, len(s)) for i := range buttons { buttons[i] = i } return func(yield func(ButtonSet) bool) { if !yield(ButtonSet{Joltages: make([]int, joltageLen), Parity: make([]int, joltageLen)}) { return } for pattern := range generateCombinations(buttons) { joltages := make([]int, joltageLen) p2 := make([]int, len(pattern)) for i := range p2 { buttonIndex := pattern[i] button := s[buttonIndex] for _, b := range button { joltages[b] += 1 } } parity := ParityOf(joltages) if !yield(ButtonSet{Joltages: joltages, Parity: parity, ButtonsPresses: len(pattern)}) { return } } } } func generateCombinations[T ~[]E, E any](arr T) iter.Seq[T] { return func(yield func(T) bool) { for i := range arr { combs := combinatorics.NewCombinator(arr, i+1) for value := range combs.Values() { if !yield(value) { return } } } } }