mgla

Advent of Code - 2025 - Day 10

Dec 10th, 2025
404
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 9.58 KB | None | 0 0
  1. using System.Diagnostics;
  2.  
  3. var input = await File.ReadAllLinesAsync("input.txt");
  4.  
  5. var machines = new List<Machine>();
  6.  
  7. foreach (var line in input)
  8. {
  9.     var split = line.Split(' ');
  10.     var requiredLights = new List<int>();
  11.  
  12.     for (var i = split[0].IndexOf('#'); i > -1; i = split[0].IndexOf('#', i + 1))
  13.     {
  14.         requiredLights.Add(i - 1);
  15.     }
  16.  
  17.     var buttons = new List<int[]>();
  18.  
  19.     for (var i = 1; i < split.Length - 1; i++)
  20.     {
  21.         var button = split[i][1..^1].Split(',').Select(int.Parse).ToArray();
  22.         buttons.Add(button);
  23.     }
  24.  
  25.     var joltages = split[^1][1..^1].Split(',').Select(int.Parse).ToArray();
  26.  
  27.     var machine = new Machine([.. requiredLights], [.. buttons], joltages, []);
  28.     machines.Add(machine);
  29. }
  30.  
  31. var sw = Stopwatch.StartNew();
  32.  
  33. var part1 = 0;
  34.  
  35. for (var i = 0; i < machines.Count; i++)
  36. {
  37.     var machine = machines[i];
  38.  
  39.     // Convert button arrays to bitmasks for O(1) toggling
  40.     var buttonMasks = new long[machine.Buttons.Length];
  41.     for (var b = 0; b < machine.Buttons.Length; b++)
  42.     {
  43.         var mask = 0L;
  44.         foreach (var light in machine.Buttons[b])
  45.         {
  46.             mask |= 1L << light;
  47.         }
  48.         buttonMasks[b] = mask;
  49.     }
  50.  
  51.     // Convert required lights to bitmask
  52.     var requiredMask = 0L;
  53.     foreach (var light in machine.RequiredLights)
  54.     {
  55.         requiredMask |= 1L << light;
  56.     }
  57.  
  58.     var minPresses = SolveDFS(buttonMasks, requiredMask, machine.Buttons.Length);
  59.  
  60.     part1 += minPresses;
  61. }
  62.  
  63. sw.Stop();
  64. var part1Time = sw.Elapsed;
  65.  
  66. Console.WriteLine($"Part 1: {part1}, Time: {part1Time.TotalMilliseconds:F3} ms");
  67.  
  68. sw.Restart();
  69.  
  70. var part2 = 0;
  71.  
  72. for (var i = 0; i < machines.Count; i++)
  73. {
  74.     var machine = machines[i];
  75.     var joltages = machine.Joltages.Length;
  76.     var buttons = machine.Buttons.Length;
  77.  
  78.     var matrix = new int[joltages, buttons + 1];
  79.  
  80.     for (var eq = 0; eq < joltages; eq++)
  81.     {
  82.         for (var btn = 0; btn < buttons; btn++)
  83.         {
  84.             if (machine.Buttons[btn].Contains(eq))
  85.             {
  86.                 matrix[eq, btn] = 1;
  87.             }
  88.         }
  89.         matrix[eq, buttons] = machine.Joltages[eq];
  90.     }
  91.  
  92.     var solution = SolveGaussianElimination(matrix, buttons, joltages);
  93.  
  94.     if (solution != null)
  95.     {
  96.         part2 += solution.Sum();
  97.     }
  98. }
  99.  
  100. sw.Stop();
  101. var part2Time = sw.Elapsed;
  102.  
  103. Console.WriteLine($"Part 2: {part2}, Time: {part2Time.TotalMilliseconds:F3} ms");
  104.  
  105. static int SolveDFS(long[] buttonMasks, long requiredMask, int maxButtons)
  106. {
  107.     var stack = new Stack<(long state, int presses)>();
  108.     var seen = new Dictionary<long, int>(); // state -> min presses to reach it
  109.  
  110.     stack.Push((0L, 0));
  111.     seen[0L] = 0;
  112.  
  113.     var minPresses = int.MaxValue;
  114.  
  115.     while (stack.Count > 0)
  116.     {
  117.         var (state, presses) = stack.Pop();
  118.  
  119.         // Pruning: skip if we've already found a better solution
  120.         if (presses >= minPresses)
  121.         {
  122.             continue;
  123.         }
  124.  
  125.         // Check if we've reached the goal
  126.         if (state == requiredMask)
  127.         {
  128.             minPresses = Math.Min(minPresses, presses);
  129.             continue;
  130.         }
  131.  
  132.         // Pruning: don't explore beyond max buttons
  133.         if (presses >= maxButtons)
  134.         {
  135.             continue;
  136.         }
  137.  
  138.         // Try each button
  139.         for (var b = 0; b < buttonMasks.Length; b++)
  140.         {
  141.             var newState = state ^ buttonMasks[b]; // XOR for toggle
  142.             var newPresses = presses + 1;
  143.  
  144.             // Only explore if we haven't seen this state with fewer presses
  145.             if (!seen.TryGetValue(newState, out var prevPresses) || newPresses < prevPresses)
  146.             {
  147.                 seen[newState] = newPresses;
  148.                 stack.Push((newState, newPresses));
  149.             }
  150.         }
  151.     }
  152.  
  153.     return minPresses;
  154. }
  155.  
  156. static int[]? SolveGaussianElimination(int[,] matrix, int buttons, int joltages)
  157. {
  158.     var pivotCols = new List<int>();
  159.     var currentRow = 0;
  160.  
  161.     // Forward elimination
  162.     for (var col = 0; col < buttons && currentRow < joltages; col++)
  163.     {
  164.         // Find pivot
  165.         var pivotRow = -1;
  166.         for (var row = currentRow; row < joltages; row++)
  167.         {
  168.             if (matrix[row, col] != 0)
  169.             {
  170.                 pivotRow = row;
  171.                 break;
  172.             }
  173.         }
  174.  
  175.         if (pivotRow == -1)
  176.         {
  177.             continue;
  178.         }
  179.  
  180.         // Swap rows
  181.         if (pivotRow != currentRow)
  182.         {
  183.             for (var j = 0; j <= buttons; j++)
  184.             {
  185.                 (matrix[currentRow, j], matrix[pivotRow, j]) = (matrix[pivotRow, j], matrix[currentRow, j]);
  186.             }
  187.         }
  188.  
  189.         pivotCols.Add(col);
  190.  
  191.         // Eliminate below
  192.         for (var row = currentRow + 1; row < joltages; row++)
  193.         {
  194.             if (matrix[row, col] != 0)
  195.             {
  196.                 var factor = matrix[row, col];
  197.                 var pivotVal = matrix[currentRow, col];
  198.  
  199.                 for (var j = col; j <= buttons; j++)
  200.                 {
  201.                     matrix[row, j] = matrix[row, j] * pivotVal - matrix[currentRow, j] * factor;
  202.                 }
  203.  
  204.                 // Reduce by GCD to prevent coefficient explosion
  205.                 var gcd = 0;
  206.                 for (var j = col; j <= buttons; j++)
  207.                 {
  208.                     if (matrix[row, j] != 0)
  209.                     {
  210.                         gcd = gcd == 0 ? Math.Abs(matrix[row, j]) : GCD(gcd, Math.Abs(matrix[row, j]));
  211.                     }
  212.                 }
  213.                 if (gcd > 1)
  214.                 {
  215.                     for (var j = col; j <= buttons; j++)
  216.                     {
  217.                         matrix[row, j] /= gcd;
  218.                     }
  219.                 }
  220.             }
  221.         }
  222.  
  223.         currentRow++;
  224.     }
  225.  
  226.     // Find free variables
  227.     var pivotSet = new HashSet<int>(pivotCols);
  228.     var freeVars = new List<int>();
  229.     for (var i = 0; i < buttons; i++)
  230.     {
  231.         if (!pivotSet.Contains(i))
  232.         {
  233.             freeVars.Add(i);
  234.         }
  235.     }
  236.  
  237.     // No free variables - unique solution or no solution
  238.     if (freeVars.Count == 0)
  239.     {
  240.         return TrySolution(matrix, buttons, joltages, pivotCols, freeVars, []);
  241.     }
  242.  
  243.     int[]? bestSolution = null;
  244.     var bestSum = int.MaxValue;
  245.  
  246.     // Compute search bounds based on free variable count
  247.     var maxVal = freeVars.Count switch
  248.     {
  249.         1 => Math.Max(matrix[0, buttons] * 3, 2000),
  250.         2 => ComputeMaxVal(matrix, pivotCols),
  251.         3 => 500,
  252.         4 => 200,
  253.         5 => 100,
  254.         _ => 0
  255.     };
  256.  
  257.     if (freeVars.Count > 5)
  258.     {
  259.         return TrySolution(matrix, buttons, joltages, pivotCols, freeVars, new int[freeVars.Count]);
  260.     }
  261.  
  262.     var current = new int[freeVars.Count];
  263.     SearchFreeVars(0);
  264.  
  265.     return bestSolution;
  266.  
  267.     void SearchFreeVars(int depth)
  268.     {
  269.         if (depth == freeVars.Count)
  270.         {
  271.             var solution = TrySolution(matrix, buttons, joltages, pivotCols, freeVars, current);
  272.             if (solution != null)
  273.             {
  274.                 var totalPresses = solution.Sum();
  275.                 if (totalPresses < bestSum)
  276.                 {
  277.                     bestSum = totalPresses;
  278.                     bestSolution = solution;
  279.                 }
  280.             }
  281.             return;
  282.         }
  283.  
  284.         var limit = freeVars.Count == 1 ? maxVal + 1 : maxVal;
  285.  
  286.         // Pruning: skip if current partial sum already exceeds best
  287.         var partialSum = 0;
  288.         for (var i = 0; i < depth; i++)
  289.         {
  290.             partialSum += current[i];
  291.         }
  292.  
  293.         for (var v = 0; v < limit; v++)
  294.         {
  295.             // Early exit if partial sum exceeds best
  296.             if (partialSum + v >= bestSum)
  297.                 break;
  298.  
  299.             current[depth] = v;
  300.             SearchFreeVars(depth + 1);
  301.         }
  302.     }
  303.  
  304.     static int ComputeMaxVal(int[,] matrix, List<int> pivotCols)
  305.     {
  306.         var maxVal = 0;
  307.         var buttons = matrix.GetLength(1) - 1;
  308.         for (var row = 0; row < pivotCols.Count; row++)
  309.         {
  310.             maxVal = Math.Max(maxVal, Math.Abs(matrix[row, buttons]));
  311.         }
  312.         return Math.Min(Math.Max(maxVal * 2, 500), 2000);
  313.     }
  314. }
  315.  
  316. static int[]? TrySolution(int[,] matrix, int buttons, int joltages, List<int> pivotCols, List<int> freeVars, int[] freeValues)
  317. {
  318.     var solution = new int[buttons];
  319.  
  320.     // Set free variables
  321.     for (var i = 0; i < freeVars.Count; i++)
  322.     {
  323.         solution[freeVars[i]] = freeValues[i];
  324.     }
  325.  
  326.     // Back substitution for pivot variables
  327.     for (var i = pivotCols.Count - 1; i >= 0; i--)
  328.     {
  329.         var row = i;
  330.         var col = pivotCols[i];
  331.         var total = matrix[row, buttons];
  332.  
  333.         for (var j = col + 1; j < buttons; j++)
  334.         {
  335.             total -= matrix[row, j] * solution[j];
  336.         }
  337.  
  338.         if (matrix[row, col] == 0 || total % matrix[row, col] != 0)
  339.             return null;
  340.  
  341.         var val = total / matrix[row, col];
  342.         if (val < 0) return null;
  343.  
  344.         solution[col] = val;
  345.     }
  346.  
  347.     // Verify solution
  348.     for (var i = 0; i < joltages; i++)
  349.     {
  350.         var sum = 0;
  351.         for (var j = 0; j < buttons; j++)
  352.         {
  353.             sum += matrix[i, j] * solution[j];
  354.         }
  355.         if (sum != matrix[i, buttons]) return null;
  356.     }
  357.  
  358.     return solution;
  359. }
  360.  
  361. static int GCD(int a, int b)
  362. {
  363.     while (b != 0)
  364.     {
  365.         int temp = b;
  366.         b = a % b;
  367.         a = temp;
  368.     }
  369.     return a;
  370. }
  371.  
  372. record struct Machine(int[] RequiredLights, int[][] Buttons, int[] Joltages, int[] Lights);
Advertisement
Add Comment
Please, Sign In to add comment