| 1 | """ |
||
| 2 | Project Euler Problem 12: Highly Divisible Triangular Number |
||
| 3 | ============================================================ |
||
| 4 | |||
| 5 | .. module:: solutions.problem12 |
||
| 6 | :synopsis: My solution to problem #12. |
||
| 7 | |||
| 8 | The source code for this problem can be |
||
| 9 | `found here <https://bitbucket.org/nekedome/project-euler/src/master/solutions/problem12.py>`_. |
||
| 10 | |||
| 11 | Problem Statement |
||
| 12 | ################# |
||
| 13 | |||
| 14 | The sequence of triangle numbers is generated by adding the natural numbers. So the :math:`7^{th}` triangle number would |
||
|
0 ignored issues
–
show
|
|||
| 15 | be |
||
| 16 | :math:`1+2+3+4+5+6+7 = 28`. |
||
| 17 | |||
| 18 | The first ten terms would be: |
||
| 19 | :math:`1,3,6,10,15,21,28,36,45,55,\\dots` |
||
| 20 | |||
| 21 | Let us list the factors of the first seven triangle numbers: |
||
| 22 | |||
| 23 | .. math:: |
||
| 24 | |||
| 25 | 1&: 1 \\\\ |
||
| 26 | 3&: 1,3 \\\\ |
||
| 27 | 6&: 1,2,3,6 \\\\ |
||
| 28 | 10&: 1,2,5,10 \\\\ |
||
| 29 | 15&: 1,3,5,15 \\\\ |
||
| 30 | 21&: 1,3,7,21 \\\\ |
||
| 31 | 28&: 1,2,4,7,14,28 |
||
| 32 | |||
| 33 | We can see that :math:`28` is the first triangle number to have over five divisors. |
||
| 34 | |||
| 35 | What is the value of the first triangle number to have over five hundred divisors? |
||
| 36 | |||
| 37 | Solution Discussion |
||
| 38 | ################### |
||
| 39 | |||
| 40 | This solution uses a sieve to incrementally build up the count of divisors :math:`d(x)` for all :math:`x` up to some |
||
|
0 ignored issues
–
show
|
|||
| 41 | pre-determined upper-bound. With this sieve, the problem is easily solvable. |
||
| 42 | |||
| 43 | All triangle numbers are of the form: |
||
| 44 | :math:`T_n = 1 + 2 + \\dots + n` |
||
| 45 | which can be re-expressed in the closed form: |
||
| 46 | :math:`T_n = \\frac{n \\times (n + 1)}{2}` |
||
| 47 | |||
| 48 | Importantly, this expression can be de-composed into pair-wise co-prime components. |
||
| 49 | |||
| 50 | Case (:math:`n` is even): |
||
| 51 | :math:`T_n = \\frac{n}{2} \\times (n + 1)` |
||
| 52 | |||
| 53 | Case (:math:`n` is odd): |
||
| 54 | :math:`T_n = n \\times (\\frac{n + 1}{2})` |
||
| 55 | |||
| 56 | It should be easy to see that only one term in each case is divisible by :math:`2`, so prior to dividing out by |
||
|
0 ignored issues
–
show
|
|||
| 57 | :math:`2` we have: |
||
| 58 | |||
| 59 | * (:math:`n` is even) n and (n + 1) |
||
| 60 | * (:math:`n` is odd) n and (n + 1) |
||
| 61 | |||
| 62 | Clearly, :math:`n` and :math:`(n + 1)` are co-prime, therefore, the two terms in each case are pair-wise co-prime. |
||
|
0 ignored issues
–
show
|
|||
| 63 | |||
| 64 | Since these terms are co-prime, the number of divisors in :math:`T_n` can be determined by the number of divisors in |
||
|
0 ignored issues
–
show
|
|||
| 65 | each term. |
||
| 66 | |||
| 67 | .. math:: |
||
| 68 | |||
| 69 | d(T_n) &= d \\bigg(n \\times \\frac{n + 1}{2} \\bigg) & \\\\ |
||
| 70 | &= d(n) \\times d \\bigg(\\frac{n + 1}{2} \\bigg) & \\; \\; \\; \\mbox{(if n is odd)} \\\\ |
||
|
0 ignored issues
–
show
|
|||
| 71 | &= d \\bigg(\\frac{n}{2} \\bigg) \\times d(n + 1) & \\; \\; \\; \\mbox{(if n is even)} |
||
|
0 ignored issues
–
show
|
|||
| 72 | |||
| 73 | So, the overall solution is to build a sieve on the number of divisors in the range :math:`n=1,\\dots,answer` |
||
|
0 ignored issues
–
show
|
|||
| 74 | |||
| 75 | Finally, we may set a reasonable upper-bound on :math:`answer` by applying the following: |
||
| 76 | |||
| 77 | .. math:: |
||
| 78 | |||
| 79 | & d(answer) \\lt 2 \\times \\sqrt{answer} \\mbox{ and } d(answer) \\gt 500 \\\\ |
||
| 80 | & \\Rightarrow 500 \\lt d(answer) \\lt 2 \\times \\sqrt{answer} \\\\ |
||
| 81 | & \\Rightarrow \\frac{500}{2} \\lt \\sqrt{answer} \\\\ |
||
| 82 | & \\Rightarrow 250 \\lt \\sqrt{answer} \\\\ |
||
| 83 | & \\Rightarrow \\sqrt{answer} \\gt 250 \\\\ |
||
| 84 | & \\Rightarrow answer \\gt 250^2 |
||
| 85 | |||
| 86 | While this range will be sufficient, it may be excessive. This algorithm computes the solution quickly enough so I won't |
||
|
0 ignored issues
–
show
|
|||
| 87 | investigate a tighter bound on :math:`n`. |
||
| 88 | |||
| 89 | Solution Implementation |
||
| 90 | ####################### |
||
| 91 | |||
| 92 | .. literalinclude:: ../../solutions/problem12.py |
||
| 93 | :language: python |
||
| 94 | :lines: 97- |
||
| 95 | """ |
||
| 96 | |||
| 97 | from lib.numbertheory import divisors_sieve, is_even |
||
| 98 | |||
| 99 | |||
| 100 | def solve(): |
||
| 101 | """ Compute the answer to Project Euler's problem #12 """ |
||
| 102 | |||
| 103 | target = 500 # target number of divisors |
||
| 104 | limit = (target // 2) ** 2 # search limit for the sieve |
||
| 105 | |||
| 106 | # Build the number of divisors sieve |
||
| 107 | divisors = divisors_sieve(limit, proper=False, aggregate="count") |
||
| 108 | sieve = {n + 1: divisors_count for n, divisors_count in enumerate(divisors)} |
||
| 109 | |||
| 110 | # Search through the sieve for the first T_n exceeding the targeted number of divisors |
||
| 111 | answer = None |
||
| 112 | for n in range(1, limit - 1): |
||
|
0 ignored issues
–
show
The name
n does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. Loading history...
|
|||
| 113 | if is_even(n): |
||
| 114 | n_divisors = sieve[n // 2] * sieve[n + 1] |
||
| 115 | else: |
||
| 116 | n_divisors = sieve[n] * sieve[(n + 1) // 2] |
||
| 117 | if n_divisors > target: |
||
| 118 | answer = n * (n + 1) // 2 |
||
| 119 | break |
||
| 120 | |||
| 121 | return answer |
||
| 122 | |||
| 123 | |||
| 124 | expected_answer = 76576500 |
||
|
0 ignored issues
–
show
The name
expected_answer does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. Loading history...
|
|||
| 125 |
This check looks for lines that are too long. You can specify the maximum line length.