AoC 2021 D7: The Treachery of Whales

TypeScript | Problem statement | Source code | Tags: Mathematics

← Previous Back to AoC Index Next →

Part 1

We are only given 1000 crabs, so a O(n2)\mathcal{O}(n^2) brute-force solution is feasible. First, obviously we want the alignment position to be between the minimum and maximum initial positions of the crabs, because moving outside that range would be more distance for all crabs. So we can iterate through all positions from min to max, and for each position calculate the total fuel cost by summing up the distances from each crab to that position.

However, we can do better than that. Picture all crabs c1..cnc_1..c_n on the number line (sorted ascending), and some point pp between c1c_1 and cnc_n. Assume that ck<p<ck+1c_k < p < c_{k+1}.

  c_1 .... c_k  p    c_{k+1} .... c_n
<-- k crabs --> ^ <-- n - k - 1 crabs -->

Then, when we move pp to the right by 1 unit, all crabs to the left of pp need to move 1 unit more, while all crabs to the right of pp need to move 1 unit less. Therefore, the total fuel cost changes by k(nk1)=2k+1nk - (n - k - 1) = 2k + 1 - n. This means that if k<(n1)/2k < (n-1)/2, moving pp to the right decreases the fuel cost, and if k>(n1)/2k > (n-1)/2, moving pp to the right increases the fuel cost. Therefore, the optimal position is at k=(n1)/2k = (n-1)/2, which is the median of the initial positions. If there's an odd number of crabs, aligning at the middle element is optimal; if there's an even number of crabs, aligning anywhere between the two middle elements, inclusive, is optimal. Selecting the median can be done in O(n)\mathcal{O}(n) time using the Quickselect algorithm, but since nn is small here, sorting the array and picking the middle element is sufficient.

Mathematically the objective is:

f(p)=i=1ncipf(p) = \sum_{i=1}^n |c_i - p|

The derivative is:

f(p)=i=1nsgn(cip)f'(p) = \sum_{i=1}^n -\text{sgn}(c_i - p)

Setting f(p)=0f'(p) = 0 gives us the same conclusion as above: we want equal number of crabs on both sides of pp, plus a 0 if nn is odd.

Part 2

The brute-force solution is still feasible, but the median idea no longer works, because moving pp by 1 unit now changes the fuel cost by more complicated amounts. However, we can still use calculus to find the optimal position. The objective is now:

f(p)=i=1ncip(cip+1)2=12i=1n(cip)2+12i=1ncipf(p) = \sum_{i=1}^n \frac{|c_i - p|(|c_i - p| + 1)}{2} = \frac{1}{2}\sum_{i=1}^n (c_i - p)^2 + \frac{1}{2}\sum_{i=1}^n |c_i - p|

The derivative is:

f(p)=i=1n(cip)12i=1nsgn(cip)=npi=1nci12i=1nsgn(cip)\begin{aligned} f'(p) &= -\sum_{i=1}^n (c_i - p) - \frac{1}{2}\sum_{i=1}^n \text{sgn}(c_i - p)\\ &= np - \sum_{i=1}^n c_i - \frac{1}{2}\sum_{i=1}^n \text{sgn}(c_i - p) \end{aligned}

If again ck<p<ck+1c_k < p < c_{k+1}, then i=1nsgn(cip)=(nk1)k=n2k1\sum_{i=1}^n \text{sgn}(c_i - p) = (n - k - 1) - k = n - 2k - 1. Setting f(p)=0f'(p) = 0 gives:

npi=1nci12(n2k1)=0p=1ni=1nci+12(12k+1n)\begin{gathered} np - \sum_{i=1}^n c_i - \frac{1}{2}(n - 2k - 1) = 0\\ p = \frac{1}{n}\sum_{i=1}^n c_i + \frac{1}{2}\left(1 - \frac{2k + 1}{n}\right) \end{gathered}

Because 1k<n1\le k< n, the term 12(12k+1n)\frac{1}{2}\left(1 - \frac{2k + 1}{n}\right) is between 1/2-1/2 and 1/21/2. Therefore, the optimal position is in the range (mean1/2,mean+1/2)(\text{mean} - 1/2, \text{mean} + 1/2). This range only contains a single integer (the mean rounded to the nearest integer); this is the optimal position.

← Previous Back to AoC Index Next →