AoC 2019 D22: Slam Shuffle
| Problem statement | Source code | Tags: MathematicsModular arithmetic
← Previous Back to AoC Index Next →
Part 1
This part can be simulated directly, which looks like:
Part 2
The scale of the problem is screaming "don't simulate me". Heck, we can't even simulate one shuffle, because the deck is cards large. Instead, we need to model the shuffles mathematically.
For a given card at position , each shuffle operation can be represented as a linear function:
- Deal into new stack:
- Cut cards:
- Deal with increment :
Note that modular arithmetic is closed under addition and multiplication, so composing these functions works just like normal function composition, even with an additional . Namely:
I wrote a class to store and compose these linear functions:
Note that instead of normal + and *, we use modular addition and multiplication. Because a * b itself may overflow long long even when both are less than mod, I used __int128_t to perform the multiplication safely. This is not portable, but works on Clang/macOS. I read up on modular multiplication techniques but decided this was simpler.
Now we can represent one shuffle sequence as a single linear function:
I refactored part 1 to use this composed function, and verified that it produced the same result.
Shuffling the deck m times is equivalent to applying the linear function m times. Because m is also on the order of , we need to do this better than time. We can use fast exponentiation, which cuts m in half each time, reducing the time complexity to .
Now we have the overall shuffle function, . Applying it to any number gives us that card's position after the whole shuffle operation. To find the card at final position , we need to solve for :
There's an easy way out. We can print out and , and type this into Wolfram Alpha:
And take the lowest positive solution.
If we want to actually implement this, we just need to compute , which is the modular multiplicative inverse of modulo . Since is prime, we can use Fermat's little theorem:
And mod_pow is binary exponentiation, which iterates over the bits of exp, multiplying by when the i-th bit is set.
Putting it all together, mod_mul(mod_inv(k_m), 2020 - b_m) is the answer.