Blog

What is Induction?

First of all, we are talking about Mathematics. Not Induction in Physics and Logic, although the latter is related to Mathematical Induction to some extent.

So what is Mathematical Induction? Induction here means to argue the proposition for a small case and then reuse it for a more complex case. To be precise an “Induction” proof must be done in a specific format. But in a very vague sense, you are proving by using simple cases inductively (in literal meaning) to argue the final goal statement. Let’s explore the example below.

Consider a {2 \times 2} chessboard and an L-shaped domino. How can you place some dominos onto the chessboard so that there is 1 specific uncovered cell (i.e. others fix one random cell which you cannot cover it)? It is trivial as you can place the domino in whatever position and it automatically leaves only 1 empty cell.

Then how about a {4 \times 4} chessboard? Can you place some dominos onto the chessboard so that there is only 1 uncovered cell too? You can try to do this in your own way. But here we want to do it in a more systematic way so that we can “reuse” the logic in the latter part. We can divide the board into 4 equal parts, that is 4 {2 \times 2} boards. As we know whenever we have a {2 \times 2} chessboard with 1 specific empty cell, we can still cover the remaining 3 cells with an L-shaped domino. Therefore, we can cover the middle with an L-shaped domino, then we have 3 {2 \times 2} chessboards with 1 blocked cell near the centre (blue crosses) and 1 {2 \times 2} chessboard with 1 preassigned empty cell (red cross).

Then how about a {2^n \times 2^n} chessboard? We can reuse the logic as above. If we can do so for a {2^{n-1} \times 2^{n-1}} chessboard, then for a {2^n \times 2^n} chessboard, we can divide the board into 4 {2^{n-1} \times 2^{n-1}} chessboards. Then we cover the centre with an L-shaped domino. It leaves us 3 {2^{n-1} \times 2^{n-1}} chessboards with 1 blocked cell near the centre and 1 {2^{n-1} \times 2^{n-1}} chessboard with 1 preassigned blocked cell, which all of them can be covered by some combinations of L-shaped dominos, as we can do it for any {2^{n-1} \times 2^{n-1}} board as we assumed.

Adopted from Proof Without Words II (Roger B. Nelsen)

So why the proof is already complete? It is because we have proved for the case of {n = 1} (i.e. a {2 \times 2} chessboard), then by above, as whenever we can cover any {2^{k-1} \times 2^{k-1}} chessboard with 1 specific empty cell (i.e. the case for {n = k - 1}), we can cover a {2^k \times 2^k} chessboard with 1 specific empty cell (i.e. the case for {n = k}).

In other words, if we have the case for {n = k} , then we know the case for {n = k + 1} is also true.

Therefore, we can do it for the case of {n = 1 + 1 = 2}, and hence the case of {n = 3}, and so on for {n = 4, 5, 6, \ldots}.

Hopefully, you can see this method is powerful to prove any statements related to positive integers. Formally, the principle of Mathematical Induction is as follows.

A statement is true for all positive integers {n} if

  1. that statement is true for {n = 1} (base case) and,
  2. assuming that statement is true for {n = k}, then that statement is true for {n = k + 1} (inductive step).

It is often described as the domino effect. To use only the base case and then repeat the inductive step, the statement is shown to be true for {n = 1, 2, 3, \ldots} and so on.

Induction Domino Effect From Wikimedia Commons (https://upload.wikimedia.org/wikipedia/commons/2/29/Induction_domino_effect.jpg)

The standard example of using such a principle is to show \displaystyle \sum_{r = 1}^{n} r^2 = 1^2 + 2^2 + 3^2 + \cdots + n^2 = \frac {n(n+1)(2n+1)} 6 for all positive integers {n}. If you are new to induction, you can try to follow the following steps.

  1. Show the statement is true for {n = 1} (base case):
    Verify \displaystyle 1^2  = \frac {1(1+1)(2+1)} 6
  2. Assuming that statement is true for {n = k}, Show that the statement is true for {n = k + 1} (inductive step):
    It is given \displaystyle 1^2 + 2^2 + 3^2 + \cdots + k^2 = \frac {k(k+1)(2k+1)} 6. If we add (k+1)^2 onto both sides, can we verify \displaystyle 1^2 + 2^2 + 3^2 + \cdots + (k+1)^2 = \frac {(k+1)(k+2)(2k+3)} 6?

It is too standard that you can even Google the answer directly. But it is always good to try it yourself first.

Although Mathematical Induction often associates with natural numbers, you can use it for negative numbers (by replacing {x} by {-x}) or even rational numbers in some cases (by using the base case {x = \frac 1 q} and the inductive step from {x = \frac k q} to {x = \frac {k+1}q}).

There are also different versions of Mathematical Induction, such as “Strong Induction” and “Backward Induction”. You can search these terms for more information.

It is very useful when you want to argue statements with discrete variables in Mathematics as well as Competitive Mathematics. Let’s do 2 examples.

Problem 1

Find a function f : \mathbf{N} \rightarrow \mathbf{N} which f(n) = 2f(n-1)^2 - 1 with f(1) = \sqrt 5. (Note that \mathbf{N} represents the set of all natural numbers. For the context here, we assume it means all positive numbers.)

To use induction to prove a formula, we need to first guess a plausible formula for f(n). For a valid formula, it must be able to produce a +1 after the squaring (and then multiplying by 2). One of such expression may be c + \frac 1 c, which you may recognise similar technique in a question like “Find the value of x^2 + \frac 1 {x^2} if x + \frac 1 x = 2023“.

However, \displaystyle \left( c + \frac 1 c \right)^2 = c^2 + 2 +\frac 1 {c^2}. To transform +2 to +1, we use \displaystyle g(c) =  \frac c 2 + \frac 1 {2c} and get \displaystyle 2\left( g(c) \right)^2 - 1 =  \frac {c^2} 2 + \frac 1 {2c^2} = g(c^2).

Now, to make the form g(d) become f(n) and g(d^2) become f(n+1), we employ the exponential function, namely substituting f(n) = g(c^{2^n}), we indeed get f(n+1) = g\left(c^{2^{n+1}}\right) = g\left( \left( c^{2^n}\right)^2\right) with d = c^{2^{n}}.

To find c, by f(1) = \sqrt 5, we need \displaystyle \sqrt 5 = f(1) = g(c^{2^1}) = \frac {c^2} 2 + \frac 1 {2c^2}. By solving, we get c^2 = 2 + \sqrt 5. Hence, \displaystyle f(n) = g(c^{2^n}) = g((c^2)^{2^{n-1}}) = g((2 + \sqrt 5)^{2^{n-1}}).

Finally, we get \displaystyle f(n) = \frac {(2 + \sqrt 5)^{2^{n-1}}} 2 + \frac 1 {2(2 + \sqrt 5)^{2^{n-1}}}.

You may wonder what is the role of Induction here. The answer is simple, after getting the formula, as we just guess the answer, we must show that the expression is really valid for the condition. Induction is the formal way to go, which is left as an exercise for readers.

Problem 2 (Source: https://math.stackexchange.com/questions/850377/for-every-positive-number-n-there-exists-a-n-digit-number-having-all-odd-di)

Prove that for every positive integer n, there exists an n-digit number which is divisible by 5^n and all digits are odd.

You can try it for some small numbers. You should get 5, 75, 375, 9375, \ldots, which all you need to get the next number is to add an extra digit in front of the previous number.

It is a strong signal that we can really use induction here. For n = 1, it is trivial as the number 5 is an (only) example. Now assume the statement is true for n = k, where k is a positive integer. Namely, we have a k-digit number a5^k where all digits are odd. To add an extra digit, we add b (10^k), where b = 1, 3, 5, 7 or 9. (Note: This is the (k+1)th digit, not kth) We want b to make the number divisible 5^{k+1}.

Therefore, we have 5^{k+1} | \left( b (10^{k}) + a5^k\right) =5^k \left( b (2^k) + a\right). As 5 is a prime, we have 5 |  \left( b (2^k) + a\right). It is the same as solving the linear congruence equation in mod 5, c b \equiv d for known numbers of c \equiv 2^k, d \equiv -a. We know that such odd b always exists by the Bézout’s identity.

We have seen some examples showing that time Induction is useful. But do not abuse it. We will see why in a future blog.

Policy Based Data Structure (PBDS)

Supposed that we are tasked with designing a SORTracker system that can stores collection of name and attractive score. The attrativeness score of a location will determine it’s ranking in the system, if two location have same score, then will compare lexicographical order to determine the order. The system support two operation.

1. Adding: scenic locations, one at a time.
2. Quering the ith best locaion of all locations already added

In order to effciently implement the above class, we may need a data structure that store scenic data, and with a way for ranking the existing data also support with dynamically add new location, then lastly, it needed to be effciently looked up as well.

We may examine the existing tools we had in C++ library,

  1. vector, a dynamic array. Clearly we unable to use due to linear complexity for inserting new element.
  2. set, a balanced tree. Support adding, while c++ do not support look up indexed element in set
  3. unordered_set, hash map. Unordered, not able to provide a rank for each element.
  4. priority_queue, heap. Support O(1) adding but also do not support look up indeded element.
  5. queue, stack, not so much useful neither.

As we can see from the above analysis, none of the C++ native container can fulfill the requirement of this system. While there do exist more advanced data structure that is able to accompoish the same task, the implementation time would be a significant concern, would be a deal breaker for a competitive programmer in a contest.

We can observe the native container, suppose that we have implemented a balanced tree our own, it should be an easy task for us to quering the ith element. This is where policy-based data structure come in handy, they are powerful tool that can customize the existing behaviour of standard container in c++. One of such example is the “ordered set” that while keeps the unique elements in sorted state, additionally suipport two log(n) operation as below

  1. order_of_key(k): number of element strictly smaller than k
  2. find_by_order(k): the kth element in a set.

In order to use Policy Based Data Strcuture, we first include the necessary header

#include <ext/pb_ds/assoc_container.hpp> 
#include <ext/pb_ds/tree_policy.hpp> 
using namespace __gnu_pbds;

The assoc_container adds more flexibility and customization to native class such as set and map, while tree_policy define policies for customizing behaviour of tree-based container. For example, “whether to allow duplicates, and nodes count stored in tree node”. The namespace is just requirement by pbds.

The policy for ordered_set is tree_order_statistics_node__update. The detail of implementation isn’t so important to understand, as we only interest in the above two index looking functions. In short, this can be achieve by storing the children nodes countf as node’s attribute in a balanced tree, is will enable fast looking up for the k-th element, (log(n) time). It would very simple binary search operation and left for reader to think about how to do so given the node count.

typedef tree<
  int ,
  null_type,   
  less<int>,   // Customizable
  rb_tree_tag, // Red-black tree used in underlying implementation
  tree_order_statistics_node_update
> ordered_set;

And a possible use scenrio would be like

ordered_set Set;
set.insert(3);
set.insert(5);
set.insert(1);

// order_of_key(x) returns the number of items in a set that are strictly smaller than x; or equivlently, the index of the first item greater or equal to x
Set.order_of_key(nums[i]);

// find_by_order(k) returns an iterator to the k-th largest element (counting from zero), 
Set.find_by_order(nums[i]);

As the end, if you are python coder, then there is ordered_set. (Yea you prob dont need to read this article).

Here are some useful link, if want investigate more

Lenstra’s elliptic curve factorisation

A factor of a positive integer {n} is another positive integer which divides {n}. A relatively simple concept, yet one that has fascinated and tormented mathematicians for centuries. How can we quickly factorise a number?

One practical method is trial division: given an integer {n} we want to factorise, we loop from {2} to {n} and repeatedly check if its a factor: if so, we divide {n} by the number as many times as possible, add it to our list of factors and continue. In the worst case, we must search every number up to {n} (if {n} is prime): this can be improved by only checking numbers up to {\sqrt n} (an exercise for the reader!).

There are other deterministic methods for factorising integers (i.e. ones which will definitely return all factors within a finite amount of time). However, there are also probabilistic methods for computing factors which, although do not guarantee factors, generally fare much better than deterministic ones as they are much likelier to return factors.

Let’s take a look at Fermat’s little theorem for a moment: it states that for any prime {p} and integer {a} which is not a multiple of {p}, {a^{p-1} \equiv 1 \pmod p} (check out CPMSoc’s number theory workshop slides to see a proof of this!). Further note that for any integer {K}, {a^{K(p-1)} = \left(a^{p-1})\right)^K \equiv 1^K = 1 \pmod p}

Our goal now is to somehow generate an integer {v} which is a multiple of {p - 1} for some prime factor {p}, so that we can guarantee {a^v - 1 \equiv 0 \pmod p}. We can then use the Euclidean algorithm to find a common divisor between {a^v - 1} and {n} (this was also covered in CPMSoc’s number theory workshop), and thus generate a factor! One important requirement is that {a^v - 1} should not be divisible by all of the prime factors, otherwise {\gcd(a^v - 1, n) = n,} which is not new information. Something to be aware of is that some integers {a} can result in {a^v - 1} being a multiple of a prime {p} even though {v} is not a multiple of {p - 1}. This is OK, and in fact means we can return a factor even earlier if we find such a {v}! A practical implementation of this is shown below in Python:

from math import gcd
import random
import sympy

# WLOG we want factorise to return a nontrivial
# factor of n (we then recursively call factorise
# on this number)
def factorise(n):
    if n == 1 or sympy.isprime(n):
        return [n]
    while True:
        a = random.randint(2, n - 1)
        g = gcd(a, n)
        if g != 1: # we've already found a factor of n
            return factorise(g) + factorise(n // g)

        x = a
        # our v is generated with a factorial
        # i.e. we check gcd(a**(b!) - 1, n)
        for b in range(2, n):
            a = pow(a, b, n) # exponentiation to factorial (mod n)
            common_divisor = gcd(a - 1, n)
            if common_divisor == n: # bad value of a, or another issue...
                break
            elif common_divisor != 1: # found factor of n
                return factorise(common_divisor) + factorise(n // common_divisor)
        else:
            return [n]
    
n = int(input("Enter your number: "))
print(f"Factors of {n}: {factorise(n)}")

This is Pollard’s p – 1 algorithm (most implementations, however, limit the factorial up to a certain integer, called the “smoothness bound”). There are some issues, however. For one, because we generate {v} with a factorial, there are some annoyances: for integers {n} where, for each prime factor {p}, all {p - 1} have the same largest prime factor, call this {b}, then when {v = b!}, {\gcd(a^v - 1, n) = n}, making it a lot less likely for a {\gcd} not equal to {n} to be achieved earlier. A more prominent issue concerns time complexity: for integers {n} where the largest prime factor of {p - 1} is not easily attainable via a factorial, this algorithm takes too long to generate an appropriate {v}. Perhaps we can find a structure different to exponentiation?

Well it turns out considering finite elliptic curves helps us! Elliptic curves are equations of the form {y^2 = x^3 + ax + b}, and finite elliptic curves are such equations but studied under a modulus.

Before we get into the maths, I’ll explain why using elliptic curves is better than traditional exponentiation: we are about to define a notion of addition in the elliptic curve’s space of solutions, which will cyclically generate new elements. Importantly, while the size of the cycle for exponentiation was, at most, {p - 1}, the maximum size of this cycle on an elliptic curve varies seemingly randomly between {p + 1 - 2\sqrt p} and {p + 1 + 2\sqrt p} (this is known as the Hasse-Weil bound). This means that its running time is not tied down by the largest prime factor of a single number (Pollard’s algorithm was tied down by {p-1}‘s largest prime factor).

Elliptic curve addition of two points (in the continuous case) is defined by forming a line between the two, finding the third point of intersection and reflecting the point across the x-axis (the new point is the result of the addition, and will always be on the elliptic curve). If the same point is added to itself, then we consider the tangent line. Note that if a vertical line is generated, then the resulting point is defined as “infinity”. An example of this is shown below:

Finite elliptic curve point addition involves the same numerical calculations, but calculated under {\pmod n}, with division defined as multiplication by the divisor’s inverse. Now we are ready. We pick a random elliptic curve under {\pmod n} and a point on it by picking random integers {x_0, y_0} and {a}, and setting {b = y_0^2 - x_0^3 - a}. Then, for increasing values of {b}, we calculate the point addition of {(x_0, y_0)} with itself {b!} times (this can be done efficiently by adding the previously calculated value {b} times). In this process, if we ever get an infinity point, we must’ve performed modular division with a value {d} where {\gcd(d, n) \ne 1}, as this is the only scenario where a unique inverse does not exist, in which case we can stop early since we’ve already found a factor of {n}.

In fact, we must eventually pass an infinity point! If we repeatedly point-add {(x_0, y_0)}, then, for some prime factor {p}, under {\pmod p} there are only a finite number of values, so we will form a cycle and eventually loop back to our starting point. Note that the last value in this cycle (before it loops around) must be the identity element of the elliptic curve, which is the infinity point, since this is the only one which results in {(x_0, y_0)} when point-added to {(x_0, y_0)}. Thus, if the {b!} we generate becomes a multiple of some of the {\pmod p} point-addition cycle lengths, then the repeated addition of {(x_0, y_0)} {b!} times must be the infinity point! Realise that this algorithm is essentially just an improved version of Pollard’s {p - 1} algorithm, using a different structure (elliptic curves) but the same principle (systematically trying to create multiples of only some of the prime factors of {n}).

Note that instead of calculating factorials all the way up to {n - 1}, we calculate them up to a smaller “smoothness” bound {B}, and randomly choose another curve and point on it if we don’t get to an infinity point. This is so we can actually exploit the seemingly random variance of elliptic curves’ cycle lengths.

Congratulations, you’ve discovered Lenstra’s Elliptic Curve Method for factorisation, the third fastest in the world! Only required a bit of number theory, right?

Competition Mathematics 102

From “Competition Mathematics 101”, you may see that Competition Mathematics has approachable academic content but with a peculiar question style. But everyone with sufficient training can become a semi-supernatural being able to tackle those problems.

Here is an introductory question. It is question A1 in Simon Marais Mathematics Competition 2022.

Let ABCD be a unit square, and let P be a point inside triangle ABC. Let E and F be the points on AB and AD, respectively, such that AEPF is a rectangle. Let x and y denote the lengths of  AE and AF, respectively. Determine a two-variable polynomial f(x,y) with the property that the area of AEPF is greater than the area of each of the quadrilaterals EBCP and FPCD if and only if f(x,y) > 0.

If you find the wording weird, you are perfectly normal, and so am I! It is often useful to represent a Geometry question with a diagram.

Then you can see the wording describes two properties:

  1. Quadrilaterals EBCP and FPCD are trapeziums. Their areas can be computed by the formula you learnt before.
  2. P lying inside triangle ABC makes the area of EBCP less than that of FPCD.

The question remains

What is a two-variable polynomial f(x,y) with the property if and only if f(x,y) > 0? 

Let’s look at a similar phasing.

Find a polynomial that k is greater than h if and only if that polynomial > 0.

So we have k > h. To make the right-hand side 0, we need to have kh > 0. So kh is the target as it is coincidentally a polynomial. Therefore, the technique of solving this problem is literally translating the weird question phasing to inequalities. Indeed, as the area of EBCP is always less than that of FPCD, we have

\displaystyle \begin{aligned} && \text{The area of }AEPF &> \text{The area of }FPCD \\ \Leftrightarrow && xy &> \frac {(1-y)(1+x)} 2 \\ \Leftrightarrow && 2xy &> (1-y)(1+x) \\ \Leftrightarrow && 2xy &> 1 + x - y - xy \\ \Leftrightarrow && 3xy - x + y - 1 &> 0 \\ \end{aligned}

So we have f(x,y) = 3xy - x + y - 1.

You may still wonder the following. Let me explain them one by one.

  1. Two-variable:
    You already have x and y, so you cannot introduce a third variable.
  2. Polynomial:
    A polynomial means you can just have some real numbers and variables, and then add, subtract and multiply them together. Here, the term does not make the question more difficult. However, if you get something such as \sqrt {x + y} - \sqrt z > 0, then you need to make it becomes \sqrt {x + y} > \sqrt z \Leftrightarrow x + y > z \Leftrightarrow x + y - z > 0.
  3. Property:
    It means some conditions. But you are solving a mathematics problem so you have some conditions. This term is conceptually useless here.
  4. If and only if:
    It means you need to have a necessary and sufficient condition. You need to go through some chapters in logic and arguments in Mathematics courses in order to fully understand it. Now, you can treat “A if and only if B” as “if A then B” and “if B then A”. For example, water boils if and only if the water reaches its boiling point.

You may feel that sometimes the difficulty of Competition Mathematics is not the content. I am sure you can do this problem if it is phrased as “Find an inequality in x and y makes the area of AEPF greater than the area of each of the quadrilaterals EBCP and FPCD”.

To practice, please do this simple arithmetic problem. You may see similar questions in high-school Mathematics Competition, but it is helpful for beginners to understand the thinking process in competition.

Calculate the value of

\displaystyle \sum_{k = 1}^{2023} \left[ \sqrt k \right] = \left[ \sqrt 1 \right] + \left[ \sqrt 2 \right] + \left[ \sqrt 3 \right] + \cdots + \left[ \sqrt {2023} \right]

where [x] is the floor function, i.e. the largest integer smaller or equal to x. For example, [1] = 1, [3.2] = 3, [– 4] = – 4 and [– 4.3] = – 5.

Here are some hints if you really need them.

  1. Prove by induction (in most competitions, the result is too standard, so you do not need to prove it. However, for beginners, it is a good basic exercise.)
    \displaystyle \sum_{p = 1}^{s} p^2 = 1^2 + 2^2 + 3^2 + \cdots + s^2 = \frac {s(s+1)(2s+1)} 6.
  2. For a given positive integer p, solve, for x,
    p \leq \sqrt x < p +1.

    Answer:
    It is
    p^2 \leq  x \leq p^2 + 2p
  3. How many of k makes:
    \left[ \sqrt k \right] = p?
  4. Expand p(2p+1).
  5. Note that:
    \sqrt{2025} = 45
  6. The answer is:
    59686

O’WEEK CONTEST SOLUTION (Programming)

Question 2: Binary Help

Your computer has forgotten binary and it needs your help to do a calculation! Luckily, the computer only needs help with calculating integers, and you won’t have to worry about any decimals.

To help the computer, you need to find the next largest power of 2 after the computer’s number.

Solution

To find the next largest power of 2 after N, one can check every ascending power of 2 until a power of 2 is found that is larger than N. This algorithm has a time complexity of O(log N), as N is up to a maximum of 10^18, which is approximately equal to 2^60.

Question 3: Counting Rectangles

While doodling in your maths grid book, a friend comes up to you and draws a large rectangle on your page. The rectangle follows the lines of the page, so that it bounds a a W by H grid of squares. Your friend challenges you to count the number of different grid-aligned rectangles you could draw inside theirs. For example, these rectangles are valid:

Solution

Brute force – count every possible top, left, bottom, right edge of rectangle. An idea of implementation is to iterate through each coordinate as 4 points and count if it’s a valid rectangle. There would be optimization can be made however it does the job.

def count_rectangles(a, b):
    count = 0
    for top in range(a):
        for left in range(b):
            for bottom in range(top + 1, a + 1):
                for right in range(left + 1, b + 1):
                    count += 1
    return count

Maths approach –

For the left and right edges, there are W + 1 possible choices for the left edge and W possible choices for the right edge (since the right edge can be to the right of the rightmost square). To avoid duplicate count, we divide it by 2.

Similarly, there are (H + 1) H choice for picking an edge for height. Result then be WH(W+1)(H+1))/4.

Question 3: Burger

Since becoming the best chef in all of Melbourne, he’s faced tough competition from others vying for his place. One challenger has been gaining popularity with a sort of circular sandwich, made with buns instead of bread. Not to be outdone, Michael has decided to make the perfect circular sandwich.

To achieve this goal, Michael starts with a rectangular slice bread measuring W mm by H mm. From this slice, he must cut out two identical circles to form the top and bottom of his sandwich. Since integers are the most perfect numbers, the circles must have an integer diameter.

Write a program to help Michael find the diameter of the largest pair of circles he can cut out of the slice of bread.

Solution

The solution involve bit knowledge of geometry, and some of common sense. First of all, by drawing the situation out, we can observe a relationship between radius and H, W. Follow this equation, there will be a method to derive a mathsmatical formula for the solution.

The programming approach would uses binary search to find the largest value of d such that the rectangle can fit inside the grid.

def can_fit(d, W, H):
    if d > W or d > H:
        return False
    elif d**2 > ((W-d)//2)**2 + ((H-d)//2)**2:
        return False
    else:
        return True

def find_largest_circle(W, H):
    l, r = 1, max(W, H) + 1
    while r - l > 1:
        m = (l + r) // 2
        if can_fit(m, W, H):
            l = m
        else:
            r = m
    return l

W = int(input("Enter the width of the grid: "))
H = int(input("Enter the height of the grid: "))

largest_radius = find_largest_circle(W, H)

print("The largest circles that can fit in the grid has a side length of", largest_radius)

Question 4 Gerrymending

For this question I do not include description, click this link to view.

Motivation

This question is referring to a scenario where there are two candidates and we need to find the segment where one of them is leading the other. By mentioning the term “segment”, it hints towards the possibility of multiple ways to solve the problem.

  • One possible approach is the “divide and conquer” method where we divide the problem into smaller subproblems and then solve them.
  • Another approach is the “prefix array” method, which is useful when the situation is static. It help us to find interval’s info more effciently.
  • Lastly, the “binary search” method may be used to find the start and end of the segment or to find the maximum difference and verify between the two candidates.

Prefix array is the most straightforward and plus this question doesn’t looks complicate. The reason being, it allows us to compute the number of votes for each candidate in any segment of the line in constant time, i.e., O(1) time complexity.

If we define A’s weight to be 1, and B’s weight to be -1 so that it cancel each other, then our task is to finding a contiguous subarray with the largest sum (represent A*1 + B * -1 = A – B!!!).

We use the prefix minima to find the smallest value seen so far in the prefix sum array, which represents the minimum value of any sub-array that within index i. By subtracting this value from the current prefix sum, we can get the maximum value of any sub-array that ends at index i. The maximum sub-array interval occurs when the prefix sum is at its highest and the prefix minima is at its lowest. So, we use the formula “max(prefix sum – prefix min)” to find the maximum sub-array interval.

def max_subarray_interval(line):
    prefix_sum = 0
    prefix_min = 0
    max_diff   = 0
    curr_diff  = 0

    # iterate over line
    for i, c in enumerate(line):
        # update vote counts
        counts[c] += 1

        # update prefix sum and prefix minima
        prefix_sum += (1 if c == 'A' else -1)
        prefix_min = min(prefix_min, prefix_sum)

        # calculate current difference and update max difference
        curr_diff = prefix_sum - prefix_min
        if curr_diff > max_diff:
            max_diff = curr_diff

    # determine winner
    winner = 'A' if counts['A'] > counts['B'] else 'B' if counts['B'] > counts['A'] else 'BOTH'

Question 5 Shape

For this question I do not include description, click this link to view.

Motivation

This question is about make observation, according to the constraint.

  • The shape is symmetric, so we only need to focus on one quadrant (top right for example)
  • At least one square along the top row needs to be filled to make the shape the correct height, and everything between them must be filled as well
  • Top left corner of the quadrant must also be filled, as well as the bottom right corner
  • The entire left column and bottom row must be filled as well (so its convex)
  • Then precede, we would like to find valid ways to build the empty hole at the top right as shown in above image.
  • The problem becomes: find a path from the top left to the bottom right of the
    grid, moving only right and down
  • At here, suppose that we pick a cell at the middle, suddenly we see that there are additionally square need to be filled.
  • Beside that, to satisfy diagonal constraint, we will see more square needs to be filled
  • Another observation would be, after moving two steps down in a row, we can no longer move two steps right.

The solutuib uses dynamic programming to count the number of valid and convex shapes in a grid. The algorithm iterates over every cell in the grid using two nested loops, and for each cell, it computes the number of valid and convex shapes that can be formed with that cell as the top left and bottom right as shown above.

a_right[0][0] = 1

for i in range(R+1):
  for j in range(C+1):
    if j > 0:
      a_right[i][j] = (a_right[i][j-1] + a_down[i][j-1]) % mod
      b_right[i][j] = (b_down[i][j-1]) % mod
    if i > 0:
      a_down[i][j] = (a_right[i-1][j]) % mod
      b_down[i][j] = (a_down[i-1][j] + b_right[i-1][j] + b_down[i-1][j]) % mod
        
def solve(r, c):
  return (a_right[r][c] + a_down[r][c] + b_right[r][c] + b_down[r][c]) % mod

print(solve(R, C))

Competition Mathematics 101

Imagining you are in a Physics Examination and you see the following question on the paper:

A bear falls from at rest at 1 m above the ground. It takes 0.451 s to reach the ground. What is the colour of the bear?

How can you solve it? Too hard? Then pretend you are a middle school student in a Geometry class. Your teacher asks you:

A boy leaves his home. He walks 100 m to the north and turns east to walk another 100 m to find his friends. After arriving, he finds that he forgot the wallet, so he turns south and walks 100 m to go home. Where does he live on the Earth?

A significant part of Competition Mathematics is like this. The first impression is mainly brutally weird “brain teasers”. The “teaser” is more to tease you about not acknowledging the existence of Competition Mathematics. Yes! The competition clique mocks you! You need to understand a few rules before starting to appreciate them. It will be reassuring in the end.

Unlike brain teasers, problems in Competition Mathematics yet have context. Just like you are sitting a regular exam. Recall the last question in the Geometry class. Drawing the figure often helps a lot. You will get a triangle with three right angles after constructing the diagram. What on earth can you get two parallel edges in a triangle? What have you missed? Your teacher probably draws triangles on a whiteboard to teach you triangles. What is the difference between the Earth and a whiteboard? Yes! The globe is round, while a whiteboard is flat. If you have paid 100% attention to your Geometry class, you may recall phases like “plane geometry” and “Euclidean”, etc. They mean that you always assume the problem appears on a flat surface (See note #1). The Earth effectively breaks this assumption. Let’s take out an orange and find where you can draw a triangle with intersecting north-pointing and south-pointing lines. The answer is at the pole because the longitude lines (the line containing all points with the same longitude) only meet at the north and south poles. Specifically, the boy lives at the south pole (probably a researcher) as he goes to the North at the beginning. 

Latitude and Longitude of the Earth, From Wikimedia Commons

Calling Competition Mathematics “brain teasers” is just an overstatement. You will get used to the style when you do enough practice. Most likely, you only see itchy questions at the edge of the understood “syllabus” with many logical twists in competitions. So what is the “syllabus”? CPMSoc provides another article covering this. But in short, it is mostly something up to level 2 UNSW pure mathematics courses. For example, all high-school stuff, single and multi-variable Calculus, Analysis, Algebra, Linear Algebra, Statistics, Complex Analysis, Combinatorics, Discrete Mathematics, Number Theory, etc. Don’t be discouraged if you have not taken enough courses to cover all. Trust me, even if you know all of them, you may still be shocked in a competition, like looking at the “brain teasers” above. Familiarity with the competition style is crucial.

Why should you consider Competition Mathematics? Who doesn’t enjoy teasing others? Furthermore, the mathematical (logical) thinking you acquired along the journey of Competition Mathematics is an underlooked tool for any job employing quantitative methods.

Practice more is the first step of doing Competition Mathematics. If you still do not know the answer to the question at the beginning, you can consider the following facts:

  • Hints 1:
    • s = ½ gt2 where s is the “falling distance” in meters, t is the time in seconds, and g is the gravity acceleration in ms–2.
    • Answer:
      Put t = 0.451 and s = 1, we have g = 9.83279.
  • Hints 2:
    • Go to the Wikipedia page of “Gravity of Earth” and see the sentence with the footnote/references 5.
    • Answer:
      The only place with such gravity acceleration is the Arctic Ocean! So the bear is a polar bear!

Competition Mathematics is a great way to make yourself more flexible in abstract thinking. If you are interested, please join activities organised by CPMSoc. More information on https://www.unswcpmsoc.com/

Note:

  • (#1) For simplicity, you can think of 2-dimensional surfaces only, i.e. flat plane (floor, blackboards and your desks, etc.). But this academic jargon includes “flat” 3-dimensional space too. It roughly assembles the “flatness” in General Relativity. 

O’Week Quiz Solution (Mathematics)

Question 1:

You and Ryan are playing a game on a triangular board with 9 empty cells. The cells at the corner lie on both edges. You take turns filling in an integer from 1 to 9 to a blank cell. Every integer can be used once only. The first gets an edge with a sum of 20 wins. Ryan is so polite, so he lets you have the second-mover advantage and go second. How can you win the game?

This is a trick question because the second player cannot win. The secret lie between (1,9), (2,8), (3,7) and (4,6) all pair up to 10 and any two pairs sum to 20, which makes “5” left behind.

Suppose Ryan fills “5” at the top. Then no matter where and what you fill (says n), Ryan can fill 10 – n into the same line (avoid the corners when he is not yet near winning). In such case, Ryan can always get a sum of 20 at the bottom line.

Question 2:

On a large paper, let A be a point 15 cm below and 8 cm on the left of the top-right corner. Fold the paper so that the top right corner covers A. What is the area of the triangle folded into the paper?

We redraw the figure as follows.

Note that BR = 8 cm, AB = 15 cm and CD is the crease created when R is folded onto A. Folding implies that CD is the perpendicular bisector of AR, so M is the midpoint of AR. By Pythagorean theorem and Inverse Pythagorean theorem (you can also use similar triangles), we have (all lengths are in cm)

RM = \frac 1 2 \sqrt{AB^2 + BR^2}= \frac {17}2
\frac 1 {BE^2} = \frac 1 {AB^2} + \frac 1{BR^2} = \frac {289} {14400} \Leftrightarrow BE = \frac {120} {17}
By the property of similar triangles, we have the ratio of areas of △CRD to △ABR is \frac {RM^2}{BE^2} = \frac {83521}{57600}.
So the required area is (\frac {83521}{57600})(\frac {(15)(8)}{2} = \frac {83521}{960}.

O’Week Quiz Solution (Programming)

Thank you for participating in the OWeek quiz – we hope you had fun! We look forward to hosting more quizzes and articles throughout the year.

Question 1

Bob and Alice are looking for the cheapest way to purchase tickets for a cross-country train ride that they have planned. They are considering a 1-day, 7-day, or 30-day pass, each with a different price.

We can use simple recursion to write a program to solve this problem. Recursion is a programming technique that refers to a process in which a function calls itself repeatedly, for this problem we can define a function takes current days and it returns the minimum cost for travel rest of days.

# - travel stores days of travel
# - cost stores the cost of each ticket
def mincostHelper(travel, costs, currDate, endDate):
    # Base case: 
    # As current date great than endDate, there is no need to travel anymore, it took 0 to complete the journey
    if currDate > endDate:
        return 0
    
    # Recursive case
    # Always return the minimum cost for travel the rest of days
    if currDate not in travel:
        # Skip this date
        return mincostHelper(travel, costs, currDate + 1, endDate)
    else:
        return min(
mincostHelper(travel, costs, currDate + 1, endDate) + costs.oneDay, 
mincostHelper(travel, costs, currDate + 7, endDate) + costs.sevenDays,
mincostHelper(travel, costs, currDate + 30, endDate) + costs.thirtyDays
       )
        

We can use Dynamic Programming (DP) to optimize our solution. DP is a technique that involves breaking down a problem into smaller sub-problems and storing the results of those sub-problems. It is primarily used to optimize problems where the solution can be decomposed into independent subproblems.

For this problem, we will make an array dp[i] that stores the cost of fulfilling a travel plan from day i to the end of the plan. If we have to travel on the ith day, we have three options: we can buy a 1-day pass (dp[i+1]), a 7-day pass (dp[i+7]), or a 30-day pass (dp[i+30]). We will choose the option that gives us the minimum cost, that is, the minimum of dp[i+1], dp[i+7] and dp[i+30]. If we don’t have to travel on the ith day, then we will not buy any pass and the cost is zero.

We only need to do a slight change to the above code, it then will save us a lot of repetitive computation,

# - travel stores days of travel
# - cost stores the cost of each ticket
# - dp[i] = minimum cost to travel rest of days from day i
def mincostHelper(travel, costs, dp, currDate, endDate):
    # Base case: 
    # As current date great than endDate, there is no need to travel anymore, it took 0 to complete the journey
    if currDate > endDate:
        return 0

    # Check if result is stored 
    if currDate in dp:
        return dp[currDate]

    # Recursive case
    # Always return the minimum cost for travel the rest of days
    if currDate not in travel:
        # Skip this date
        dp[currDate] = min(dp[currDate], dp[currDate - 1])
        return mincostHelper(travel, costs, currDate + 1, endDate)
    else:
        dp[currDate] = min(
mincostHelper(travel, costs, currDate + 1, endDate) + costs.oneDay, 
mincostHelper(travel, costs, currDate + 7, endDate) + costs.sevenDays,
mincostHelper(travel, costs, currDate + 30, endDate) + costs.thirtyDays
       )
        return dp[currDate]

        

Time Complexity: O(T) where T = maximum numbered day in travel plan. In our DP solution, we ask maximuly T questions, and each question can be answered in O(1) time knowing answer from other questions.

Space Complexity: O(T)

Question 2

Bob and Alice is looking for an optimized solution to store suicase into warehouse.

We can make several observations towards this question. Firstly, the goal is to get as many boxes inside the warehouse and the size of the box does not matter. Secondly, if there is a narrow section of the warehouse, even if it becomes wide afterwards, the boxes will still get stuck right before it. This is due to the fact that the boxes will not be able to fit through the narrow section, thus resulting in a bottleneck situation. Thirdly, it is common sense to assume that if a small box gets stuck, then bigger boxes will also get stuck.

From above observation, we seems able to construct a Greedy Solution toward this problem. Greedy algorithms are a type of algorithm that makes localized decisions in order to optimize a global goal, it make optimal decisions at each step, according to a specific set of rules, and always aim to make the best decision at that moment in time. Greedy algorithms may not exist in some cases, and if we want to use it to solve a certain question, we would need to prove it. On the otherhand, the technical Dynamic programming we used in first question examine all possible situation, thus give the optinmal solution.

We propose, by insert the boxes in acending order, we can always reach most optimized solution. To show our proposal is correct, we prove by contradiction (a high school maths technique!??).

We propose, at a certain step where we insert the boxes, we can get an optimzied solution if we choose to insert a larger box than the smallest box we have. There are two situation we have

  • If the larger box we choose stuck eariler in the “bottleneck” situation we described earlier. Than it’s always worse than we insert the smallest one first and the largest box.
  • If the larger box stuck at the same place as the smaller box, it means the warehouse’s height before that place are greater than larger box, and thus greater than smallest box. In this case, it does not matter if we put smallest first, or the larger one.