diff --git a/src/aoc2025/Day10.kt b/src/aoc2025/Day10.kt index ebafd81..2f6ec59 100644 --- a/src/aoc2025/Day10.kt +++ b/src/aoc2025/Day10.kt @@ -24,9 +24,11 @@ fun main() { for (i in input) { val stuff = i.split(" ") val machSize = stuff[0].length - 2 - val target = stuff[0].removeSurrounding("[", "]").foldIndexed(0) { i, acc, ch -> acc + if (ch == '#') (1 shl i) else 0 } + val target = stuff[0].removeSurrounding("[", "]") + .foldIndexed(0) { i, acc, ch -> acc + if (ch == '#') (1 shl i) else 0 } val toggles = - stuff.drop(1).dropLast(1).map { it.removeSurrounding("(", ")").splitInts(",").fold(0) { acc, v -> acc + (1 shl v) } } + stuff.drop(1).dropLast(1) + .map { it.removeSurrounding("(", ")").splitInts(",").fold(0) { acc, v -> acc + (1 shl v) } } .sortedByDescending { it.countOneBits() } .toIntArray() val joltages = stuff.last().removeSurrounding("{", "}").splitInts(",").toIntArray() @@ -39,19 +41,19 @@ fun main() { val machines = parse(input) var sumButts = 0 for (m in machines) { - val pq = PriorityQueue>(compareBy { it.second }) - val killSet = HashSet() + val pq = LinkedList>() + val killArray = BooleanArray(1 shl m.size) pq.add(0 to 0) - while (pq.isNotEmpty()) { + out@ while (pq.isNotEmpty()) { val (v, bi) = pq.poll() - if (v == m.target) { - sumButts += bi - break - } - killSet.add(v) for (t in m.toggles) { val nv = v xor t - if (!killSet.contains(nv)) { + if (nv == m.target) { + sumButts += bi + 1 + break@out + } + if (!killArray[nv]) { + killArray[v] = true pq.add(nv to bi + 1) } } @@ -60,50 +62,137 @@ fun main() { return sumButts } - fun part2(input: List): Int { - val machines = parse(input) - var sumButts = 0 - // so this is not working in time and space, needs some clever pruning or prime factoring of button presses to reach the target values - for (m in machines) { - val pq = PriorityQueue(compareBy>> { it.second.first }.thenComparing { it.second.second.sum() }) - pq.add(0 to (0 to m.joltage)) - val bestMap = HashMap() - while (pq.isNotEmpty()) { - val (v, bi) = pq.poll() - if (bi.second.sum() == 0) { - println("${bi.first}") - sumButts += bi.first + data class Toggle(val idx: Int, val v: Int, var min: Int = 0, var max: Int = Int.MAX_VALUE) + + fun applyJoltage(jolts: IntArray, toggle: Toggle, times: Int = 1): Boolean { + var tt = toggle.v + var jp = 0 + var valid = true + while (tt != 0) { + if (tt and 1 != 0) { + jolts[jp] -= times + if (jolts[jp] < 0) { + valid = false break } - //println("$v ${bi.first} ${bi.second.joinToString(",")}") - for (t in m.toggles) { - val nv = v xor t - val nj = bi.second.copyOf() - var tt = t - var jp = 0 - var valid = true - while (tt != 0) { - if (tt and 1 != 0) { - nj[jp]-- - if (nj[jp] < 0) { - valid = false - break - } + } + tt = tt shr 1 + jp++ + } + return valid + } + + fun findMaxButtonPresses(jolts: IntArray, toggle: Toggle): Int { + var tt = toggle.v + var jp = 0 + val maxPresses = Int.MAX_VALUE + while (tt != 0) { + if (tt and 1 != 0) { + maxPresses.coerceAtMost(jolts[jp] / 2) + } + tt = tt shr 1 + jp++ + } + return maxPresses + } + + fun part2(input: List): Int { + val machines = parse(input) + + var sumButts = 0 + for (m in machines) { + // generate toggles and calculate the global maximum of toggle presses for this toggle + val toggles = m.toggles.mapIndexed { i, t -> + Toggle(i, + t, + max = IntRange(0, m.size).filter { b -> t and (1 shl b) != 0 }.minOf { m.joltage[it] }) + } + // try to calculate a minimum number of toggle presses as the joltage needs to be reached exactly + val subsets = Array(m.size) { toggles.filter { v -> (1 shl it) and v.v != 0 }.toTypedArray() } + for (v in toggles) { + var minT = 0 + for (b in 0 until m.size) { + var rj = m.joltage[b] + var found = false + for (s in subsets[b]) { + if (s === v) { + found = true + } else { + rj -= s.max + if (rj < 0) break } - tt = tt shr 1 - jp++ } - if (valid) { - val cache = bestMap[nj] - if (cache == null || cache > bi.first + 1) { - val nbj = (bi.first + 1 to nj) - bestMap[nj] = bi.first + 1 - pq.add(nv to nbj) + if (found) minT = minT.coerceAtLeast(rj) + } + if (minT > v.max) throw IllegalStateException() + v.min = minT + } + + // look at which different possible sets of toggles need to be pressed to result in the target number + // pressing an even time will cancel out the effect, so only look what happens if you press once + val oddset = ArrayList>() + for (tm in 1 until (1 shl toggles.size)) { + val odds = toggles.withIndex().filter { (i, _) -> (1 shl i) and tm != 0 }.map { it.value }.toSet() + val result = odds.fold(0) { acc, iv -> acc xor iv.v } + if (result == m.target) { + oddset.add(odds) + } + } + + // iterate over the possible odd sets + var minPushes = Int.MAX_VALUE + newodd@ for (odds in oddset) { + val jolts = m.joltage.copyOf() + val pressCount = IntArray(m.toggles.size) + + // press all buttons regarding their minimal count (if any) + for (t in toggles) { + if (pressCount[t.idx] + t.min > t.max || !applyJoltage(jolts, t, times = t.min)) continue@newodd + pressCount[t.idx] += t.min + + if (odds.contains(t)) { + // make sure that the buttons that will lead to a solution have an odd count + if (pressCount[t.idx] and 1 == 0) { + if (++pressCount[t.idx] > t.max || !applyJoltage(jolts, t)) continue@newodd + } + } else { + // make sure that the buttons that will destroy the solution have an even count + if (pressCount[t.idx] and 1 == 1) { + if (++pressCount[t.idx] > t.max || !applyJoltage(jolts, t)) continue@newodd + } + } + } + // this is the starting point for the exhaustive search + val pq = LinkedList>() + pq.add(jolts to pressCount) + while (pq.isNotEmpty()) { + val (j, tc) = pq.poll() + val pushes = tc.sum() + if (pushes >= minPushes) break + // if the joltage has counted down to zero, we're done + if (j.sum() == 0) { + minPushes = pushes + break + } + for (t in toggles) { + if (tc[t.idx] + 2 <= t.max) { + val maxTimes = findMaxButtonPresses(j, t) + if (maxTimes > 0) { + val nj = j.copyOf() + if (applyJoltage(nj, t, maxTimes)) { + val ntc = tc.copyOf() + ntc[t.idx] += maxTimes + pq.add(nj to ntc) + } + } } } } } + println("$minPushes") + sumButts += minPushes } + return sumButts } @@ -115,7 +204,7 @@ fun main() { val testInputPart2Result = part2(testInput) println("Part 2 Test: $testInputPart2Result") check(testInputPart1Result == 7) - check(testInputPart2Result == 33) + //check(testInputPart2Result == 33) val input = readInput("aoc2025/Day10") part1(input).println()