DEV Community

Cover image for Power of Mathematics: 2021 AoC Problem #7, Part 2
BeOnDrag
BeOnDrag

Posted on

Power of Mathematics: 2021 AoC Problem #7, Part 2

(The algorithms tag is appropriate, I think)

This post will focus on the 7th problem of AoC 2021, part 2. We first discuss the straightforward method for the original problem, and then discuss the continuous version of the problem.

This post requires some knowledge of mathematics, or more specifically, quadratic functions and absolute value.

Problem #7, AoC 2021

Basic observations

The 7th problem of 2021 Advent of Code is pretty interesting. Mathematically speaking we need to find a real number xx for a certain real sequence {xi}i=1n\{x_i\}_{i=1}^n , such that the expression

S2=i=1n(xix)(xix+1)2 S_2=\sum_{i=1}^n\frac{\left(|x_i-x|\right)\left(| x_i-x|+1\right)}{2}

reaches its smallest possible value.

Multiply S2S_2 by 2 doesn't affect the xx we're looking for. So it suffices to look for the minimum of

s=i=1n(xix)(xix+1). s=\sum_{i=1}^n\left(|x_i-x|\right)\left(|x_i-x|+1\right).

Expanding the expression we easily get

s=i=1n((xix)2+xix)=i=1n(xix)2+i=1nxix=s0+S1+C2 \begin{align*} s&=\sum_{i=1}^n\left((x_i-x)^2+| x_i-x|\right)\\ &=\sum_{i=1}^n(x_i-x)^2+\sum_{i=1}^n| x_i-x|\\ &=s_0+S_1+C_2 \end{align*}

where S1=i=1nxixS_1=\sum_{i=1}^n|x_i-x| and s0=nx22(i=1nxi)xs_0=nx^2-2\left(\sum_{i=1}^nx_i\right)x . C2C_2 is just a constant i=1nxi2\sum_{i=1}^nx_i^2 , which doesn't affect our xx .

Recall that

S1=xi>xxixi<xxi S_1=\left|\sum_{x_i>x}x_i-\sum_{x_i<x}x_i\right|

Let S1S_1 reaches its minimum when x=m1=the median of xii=1nx=m_1=\text{the median of }{x_i}_{i=1}^n and s0s_0 reaches its minimum when x=m2=C1nx=m_2=\frac{C_1}{n} , where C1=i=1nxiC_1=\sum_{i=1}^nx_i is a constant. Both value is easy and quick to obtain from the computer. Denote the interval between m0m_0 and m1m_1 by II . Notice that S1S_1 and s0s_0 are both concaving up (at least not concaving down) on R\mathbb{R} , therefore the minimum of s0+S1s_0+S_1 can only be reached on II . Since for the input provided by AoC m1=309m_1=309 and m2462.508m_2\approx462.508 , which are close enough, and the original problem restricted xx to be on Z\mathbb{Z} , the code can be easily written.

# The straightforward method
from math import floor, ceil


def median(list: list[int]):
    leng = len(list)
    list.sort()
    if leng % 2 == 1:
        return list[(leng + 1) // 2]
    else:
        return (list[leng // 2] + list[leng // 2 + 1]) / 2


def method1(data: list[int]):
    med = median(data)
    mean = sum(data) / len(data)
    if med < mean:
        med = floor(med)
        mean = ceil(mean)
    else:
        med = ceil(med)
        mean = floor(mean)

    def calcVal(pos: int):
        nonlocal data
        sum = 0
        for e in data:
            dif = abs(e - pos)
            sum += (dif * (dif + 1)) // 2
        return sum
    answer = calcVal(med)
    position = med
    for i in range(med, mean + 1):
        newans = calcVal(i)
        if (newans < answer):
            answer = newans
            position = i
    print("Minimum is", answer, ", reached when x =", position)


method1([1, 2, 3, 5, 9, 12, 89])
Enter fullscreen mode Exit fullscreen mode
Minimum is 3118 , reached when x = 17
Enter fullscreen mode Exit fullscreen mode

Further Discussion

But I don't want to stop here. We assume that x1x2xnx_1\le x_2\le\dots\le x_n . Consider the graph of L(x)=S1L(x)=S_1 It is made up with n+1n+1 segments (0 of length by chance), defined on I0=(,x1],I1=[x1,x2],,In1=[xn1,xn],In=[xn,+)I_0=(-\infty,x_1],I_1=[x_1,x_2],\dots,I_{n-1}=[x_{n-1},x_n],I_n=[x_n,+\infty) respectively, whose slope being n-n at the beginning, increases by 2 per segment, gradually stopping at nn . Therefore the segment on IkI_k , denoted by k=k(x)\ell_k=\ell_k(x) , has a slope of n+2k-n+2k . We also let x0=x_0=-\infty and xn+1=+x_{n+1}=+\infty .

Consider Fk(x)=k(x)+Q(x)F_k(x)=\ell_k(x)+Q(x) , where Q(x)=s0=nx22(i=1nxi)xQ(x)=s_0=nx^2-2\left(\sum_{i=1}^nx_i\right)x . Its axis of symmetry would be dk=m2+12knd_k=m_2+\frac{1}{2}-\frac{k}{n} . When xk+1dkx_{k+1}\le d_k , Fk(x)F_k(x) decreases on its domain. When dkxkd_k\le x_k , Fk(x)F_k(x) increases. Notice that while xi{x_i} is increasing while di{d_i} is decreasing, there must be a number β\beta , where xβdβx_\beta\le d_\beta , and xβ+1dβ+1x_{\beta+1}\ge d_{\beta+1} . Then on (,xβ](-\infty,x_\beta] s=s(x)s=s(x) is decreasing, and on [xβ+1,+)[x_{\beta+1},+\infty) s(x)s(x) is increasing. This implies that the minimum of s(x)s(x) could only be reached on [xβ,xβ+1][x_\beta,x_{\beta+1}] . It's worth noticing that if x1m2+12x_1\ge m_2+\frac{1}{2} , then s(x)s(x) reaches minimum at x=d0x=d_0 ; if xnm212x_n\le m_2-\frac{1}{2} , then s(x)s(x) reaches minimum at x=dn+1x=d_{n+1} ; because the turning point of s(x)s(x) is exactly x1,x2,,xnx_1,x_2,\dots,x_n , so be only need to check s(dβ)s(d_\beta) , s(dβ+1)s(d_{\beta+1}) , s(d0)s(d_0) and s(dn+1)s(d_{n+1}) . This method is even worse for computer (has to sort the sequence and find β\beta ), but it's better mathematically (in my opinion). Also, this can work for any real xx and xix_i .

def method2(data: list[float]):
    data.sort()
    length = len(data)
    m2 = sum(data) / len(data)
    def s(x: float):
        nonlocal data
        sum = 0
        for e in data:
            dif = abs(e - x)
            sum += (dif * (dif + 1)) / 2
        return sum
    def d(i: int):
        nonlocal m2, length
        return m2 + 1 / 2 - i / length
    def findBeta():
        nonlocal data, m2
        for i in range(1, length):
            if data[i] >= d(i):
                return i - 1
        return length
    beta = findBeta()
    checklist = [beta + 1, 0, length]
    reachmin = 0
    minimum = s(d(beta))
    for i in checklist:
        newval = s(d(i))
        if newval < minimum:
            reachmin = d(i)
            minimum = newval
    print("Minimum is", minimum, ", reached when x =", reachmin)


method2([1, 2, 3, 5, 9, 12, 89])
Enter fullscreen mode Exit fullscreen mode
Minimum is 3117.9821428571427 , reached when x = 16.928571428571427
Enter fullscreen mode Exit fullscreen mode

Beautiful, isn't it?

Top comments (1)

Collapse
 
beondrag profile image
BeOnDrag • Edited

Using the continuous version the best potision to align the crabs (my input data) is 462.40299999999996, where the fuel cost reaches the minimum of 96864153.79550003. A lot less than the "correct answer" 96864235!