add a whole bunch of problems

This commit is contained in:
filifa 2025-04-09 20:24:00 -04:00
parent 3ac699276b
commit b80145e70e
8 changed files with 960 additions and 0 deletions

103
problem0011.ipynb Normal file
View File

@ -0,0 +1,103 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "124fe12d",
"metadata": {},
"source": [
"# [Largest Product in a Grid](https://projecteuler.net/problem=11)\n",
"\n",
"Here's that whole grid in a NumPy array."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "7d93e5b9",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"grid = np.array([[08,02,22,97,38,15,00,40,00,75,04,05,07,78,52,12,50,77,91,08],\n",
" [49,49,99,40,17,81,18,57,60,87,17,40,98,43,69,48,04,56,62,00],\n",
" [81,49,31,73,55,79,14,29,93,71,40,67,53,88,30,03,49,13,36,65],\n",
" [52,70,95,23,04,60,11,42,69,24,68,56,01,32,56,71,37,02,36,91],\n",
" [22,31,16,71,51,67,63,89,41,92,36,54,22,40,40,28,66,33,13,80],\n",
" [24,47,32,60,99,03,45,02,44,75,33,53,78,36,84,20,35,17,12,50],\n",
" [32,98,81,28,64,23,67,10,26,38,40,67,59,54,70,66,18,38,64,70],\n",
" [67,26,20,68,02,62,12,20,95,63,94,39,63,08,40,91,66,49,94,21],\n",
" [24,55,58,05,66,73,99,26,97,17,78,78,96,83,14,88,34,89,63,72],\n",
" [21,36,23,09,75,00,76,44,20,45,35,14,00,61,33,97,34,31,33,95],\n",
" [78,17,53,28,22,75,31,67,15,94,03,80,04,62,16,14,09,53,56,92],\n",
" [16,39,05,42,96,35,31,47,55,58,88,24,00,17,54,24,36,29,85,57],\n",
" [86,56,00,48,35,71,89,07,05,44,44,37,44,60,21,58,51,54,17,58],\n",
" [19,80,81,68,05,94,47,69,28,73,92,13,86,52,17,77,04,89,55,40],\n",
" [04,52,08,83,97,35,99,16,07,97,57,32,16,26,26,79,33,27,98,66],\n",
" [88,36,68,87,57,62,20,72,03,46,33,67,46,55,12,32,63,93,53,69],\n",
" [04,42,16,73,38,25,39,11,24,94,72,18,08,46,29,32,40,62,76,36],\n",
" [20,69,36,41,72,30,23,88,34,62,99,69,82,67,59,85,74,04,36,16],\n",
" [20,73,35,29,78,31,90,01,74,31,49,71,48,86,81,16,23,57,05,54],\n",
" [01,70,54,71,83,51,54,69,16,92,33,48,61,43,52,01,89,19,67,48]])"
]
},
{
"cell_type": "markdown",
"id": "d4924c1d",
"metadata": {},
"source": [
"Now we'll just iterate through every 4x4 subgrid of this grid, and for each subgrid, compute the product of the rows, columns, and diagonals, and record the largest product we see."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "7ac0e53c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"70600674\n"
]
}
],
"source": [
"maximum = 0\n",
"for i in range(0, 16):\n",
" for j in range(0, 16):\n",
" subgrid = grid[i:i+4, j:j+4]\n",
" \n",
" cols = subgrid.prod(axis=0)\n",
" rows = subgrid.prod(axis=1)\n",
" diag = np.diag(subgrid).prod()\n",
" anti_diag = np.diag(np.fliplr(subgrid)).prod()\n",
" \n",
" maximum = max(maximum, *cols, *rows, diag, anti_diag)\n",
"\n",
"print(maximum)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

173
problem0013.ipynb Normal file
View File

@ -0,0 +1,173 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6c851d8c",
"metadata": {},
"source": [
"# [Large Sum](https://projecteuler.net/problem=13)\n",
"\n",
"Nothing particularly interesting here. Here's all the numbers in a list."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "0bed92ce",
"metadata": {},
"outputs": [],
"source": [
" nums = [37107287533902102798797998220837590246510135740250,\n",
" 46376937677490009712648124896970078050417018260538,\n",
" 74324986199524741059474233309513058123726617309629,\n",
" 91942213363574161572522430563301811072406154908250,\n",
" 23067588207539346171171980310421047513778063246676,\n",
" 89261670696623633820136378418383684178734361726757,\n",
" 28112879812849979408065481931592621691275889832738,\n",
" 44274228917432520321923589422876796487670272189318,\n",
" 47451445736001306439091167216856844588711603153276,\n",
" 70386486105843025439939619828917593665686757934951,\n",
" 62176457141856560629502157223196586755079324193331,\n",
" 64906352462741904929101432445813822663347944758178,\n",
" 92575867718337217661963751590579239728245598838407,\n",
" 58203565325359399008402633568948830189458628227828,\n",
" 80181199384826282014278194139940567587151170094390,\n",
" 35398664372827112653829987240784473053190104293586,\n",
" 86515506006295864861532075273371959191420517255829,\n",
" 71693888707715466499115593487603532921714970056938,\n",
" 54370070576826684624621495650076471787294438377604,\n",
" 53282654108756828443191190634694037855217779295145,\n",
" 36123272525000296071075082563815656710885258350721,\n",
" 45876576172410976447339110607218265236877223636045,\n",
" 17423706905851860660448207621209813287860733969412,\n",
" 81142660418086830619328460811191061556940512689692,\n",
" 51934325451728388641918047049293215058642563049483,\n",
" 62467221648435076201727918039944693004732956340691,\n",
" 15732444386908125794514089057706229429197107928209,\n",
" 55037687525678773091862540744969844508330393682126,\n",
" 18336384825330154686196124348767681297534375946515,\n",
" 80386287592878490201521685554828717201219257766954,\n",
" 78182833757993103614740356856449095527097864797581,\n",
" 16726320100436897842553539920931837441497806860984,\n",
" 48403098129077791799088218795327364475675590848030,\n",
" 87086987551392711854517078544161852424320693150332,\n",
" 59959406895756536782107074926966537676326235447210,\n",
" 69793950679652694742597709739166693763042633987085,\n",
" 41052684708299085211399427365734116182760315001271,\n",
" 65378607361501080857009149939512557028198746004375,\n",
" 35829035317434717326932123578154982629742552737307,\n",
" 94953759765105305946966067683156574377167401875275,\n",
" 88902802571733229619176668713819931811048770190271,\n",
" 25267680276078003013678680992525463401061632866526,\n",
" 36270218540497705585629946580636237993140746255962,\n",
" 24074486908231174977792365466257246923322810917141,\n",
" 91430288197103288597806669760892938638285025333403,\n",
" 34413065578016127815921815005561868836468420090470,\n",
" 23053081172816430487623791969842487255036638784583,\n",
" 11487696932154902810424020138335124462181441773470,\n",
" 63783299490636259666498587618221225225512486764533,\n",
" 67720186971698544312419572409913959008952310058822,\n",
" 95548255300263520781532296796249481641953868218774,\n",
" 76085327132285723110424803456124867697064507995236,\n",
" 37774242535411291684276865538926205024910326572967,\n",
" 23701913275725675285653248258265463092207058596522,\n",
" 29798860272258331913126375147341994889534765745501,\n",
" 18495701454879288984856827726077713721403798879715,\n",
" 38298203783031473527721580348144513491373226651381,\n",
" 34829543829199918180278916522431027392251122869539,\n",
" 40957953066405232632538044100059654939159879593635,\n",
" 29746152185502371307642255121183693803580388584903,\n",
" 41698116222072977186158236678424689157993532961922,\n",
" 62467957194401269043877107275048102390895523597457,\n",
" 23189706772547915061505504953922979530901129967519,\n",
" 86188088225875314529584099251203829009407770775672,\n",
" 11306739708304724483816533873502340845647058077308,\n",
" 82959174767140363198008187129011875491310547126581,\n",
" 97623331044818386269515456334926366572897563400500,\n",
" 42846280183517070527831839425882145521227251250327,\n",
" 55121603546981200581762165212827652751691296897789,\n",
" 32238195734329339946437501907836945765883352399886,\n",
" 75506164965184775180738168837861091527357929701337,\n",
" 62177842752192623401942399639168044983993173312731,\n",
" 32924185707147349566916674687634660915035914677504,\n",
" 99518671430235219628894890102423325116913619626622,\n",
" 73267460800591547471830798392868535206946944540724,\n",
" 76841822524674417161514036427982273348055556214818,\n",
" 97142617910342598647204516893989422179826088076852,\n",
" 87783646182799346313767754307809363333018982642090,\n",
" 10848802521674670883215120185883543223812876952786,\n",
" 71329612474782464538636993009049310363619763878039,\n",
" 62184073572399794223406235393808339651327408011116,\n",
" 66627891981488087797941876876144230030984490851411,\n",
" 60661826293682836764744779239180335110989069790714,\n",
" 85786944089552990653640447425576083659976645795096,\n",
" 66024396409905389607120198219976047599490197230297,\n",
" 64913982680032973156037120041377903785566085089252,\n",
" 16730939319872750275468906903707539413042652315011,\n",
" 94809377245048795150954100921645863754710598436791,\n",
" 78639167021187492431995700641917969777599028300699,\n",
" 15368713711936614952811305876380278410754449733078,\n",
" 40789923115535562561142322423255033685442488917353,\n",
" 44889911501440648020369068063960672322193204149535,\n",
" 41503128880339536053299340368006977710650566631954,\n",
" 81234880673210146739058568557934581403627822703280,\n",
" 82616570773948327592232845941706525094512325230608,\n",
" 22918802058777319719839450180888072429661980811197,\n",
" 77158542502016545090413245809786882778948721859617,\n",
" 72107838435069186155435662884062257473692284509516,\n",
" 20849603980134001723930671666823555245252804609722,\n",
" 53503534226472524250874054075591789781264330331690]"
]
},
{
"cell_type": "markdown",
"id": "745ec1fc",
"metadata": {},
"source": [
"Now we'll just sum them up and extract the first 10 digits."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4219a904",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5537376230"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"int(str(sum(nums))[:10])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

116
problem0015.ipynb Normal file
View File

@ -0,0 +1,116 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "6cc372f3",
"metadata": {},
"source": [
"# [Lattice Paths](https://projecteuler.net/problem=15)\n",
"\n",
"Let $f(m, n)$ equal the number of routes in an $m \\times n$ grid. Intuitively, if you start at the top left corner, you immediately have two choices - you can go down or right. If you go down, there are $f(m-1,n)$ routes in the $(m - 1) \\times n$ subgrid; if you go right, there are $f(m,n-1)$ routes in the $m \\times (n-1)$ subgrid. Therefore\n",
"$$f(m,n) = f(m-1,n) + f(m,n-1)$$\n",
"\n",
"There are two trivial base cases: $f(m,0) = 1$ for any $m$ and $f(0,n) = 1$ for any $n$. From these facts, you can write a simple [memoized](https://en.wikipedia.org/wiki/Memoization) recursive function to solve this problem."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "9878534a",
"metadata": {},
"outputs": [],
"source": [
"from functools import cache\n",
"\n",
"@cache\n",
"def f(m, n):\n",
" if m == 0:\n",
" return 1\n",
" if n == 0:\n",
" return 1\n",
" \n",
" return f(m - 1, n) + f(m, n - 1)"
]
},
{
"cell_type": "markdown",
"id": "5beab0ed",
"metadata": {},
"source": [
"Then the answer is given by"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "f21257bc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"137846528820"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f(20,20)"
]
},
{
"cell_type": "markdown",
"id": "3561530b",
"metadata": {},
"source": [
"However, we can do better and give a non-recursive formula for $f$. It turns out, for an $m \\times n$ grid, there are $\\binom{m+n}{m}$ possible routes. Here's an outline of how you can prove this [inductively](https://en.wikipedia.org/wiki/Mathematical_induction):\n",
"\n",
"1. Prove $f(m, 0) = \\binom{m+0}{m} = 1$ for all $m$\n",
" 1. Prove $f(0, 0) = \\binom{0 + 0}{0} = 1$\n",
" 2. Assuming $f(j, 0) = \\binom{j+0}{j} = 1$ for some $j$, prove $f(j+1,0) = \\binom{j+1+0}{j+1} = 1$\n",
"2. Assuming there exists some $k$ such that for all $m$, $f(m, k) = \\binom{m + k}{m}$, prove $f(m, k+1) = \\binom{m + k + 1}{m}$ for all $m$\n",
" 1. Prove $f(0, k+1) = \\binom{0+k+1}{0} = 1$\n",
" 2. Assuming $f(j, k+1) = \\binom{j+k+1}{j}$ for some $j$, prove $f(j+1,k+1) = \\binom{j+k+2}{j+1}$\n",
"\n",
"Note that there are *three* inductive proofs here - an overall induction on $m$ and nested inductions in both the base case and the induction step (yo dawg, I heard you like induction...).\n",
"\n",
"One useful identity for proving this, particularly in step 2, comes from [Pascal's triangle](https://en.wikipedia.org/wiki/Pascal%27s_triangle):\n",
"$$\\binom{n}{k} = \\binom{n-1}{k} + \\binom{n-1}{k-1}$$"
]
},
{
"cell_type": "markdown",
"id": "2c6e7d8c",
"metadata": {},
"source": [
"## Relevant sequences\n",
"* Central binomial coefficients: [A000984](https://oeis.org/A000984)\n",
"* General formula: [A046899](https://oeis.org/A046899)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

68
problem0016.ipynb Normal file
View File

@ -0,0 +1,68 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "78b44de2",
"metadata": {},
"source": [
"# [Power Digit Sum](https://projecteuler.net/problem=16)\n",
"\n",
"Since Python/SageMath support integers of arbitrary size, this problem is pretty straightforward. Just compute $2^{1000}$ directly and sum the digits up."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "d3a790fe",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1366\n"
]
}
],
"source": [
"n = 2 ^ 1000\n",
"total = 0\n",
"while n != 0:\n",
" total += n % 10\n",
" n //= 10\n",
"\n",
"print(total)"
]
},
{
"cell_type": "markdown",
"id": "60ba69ac",
"metadata": {},
"source": [
"## Relevant sequences\n",
"* Sums of digits of $2^n$: [A001370](https://oeis.org/A001370)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

147
problem0017.ipynb Normal file
View File

@ -0,0 +1,147 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fb5ffc43",
"metadata": {},
"source": [
"# [Number Letter Counts](https://projecteuler.net/problem=17)\n",
"\n",
"Time to think about the rules for [English numerals](https://en.wikipedia.org/wiki/English_numerals), which you probably know intuitively but have never had to explain to a computer.\n",
"\n",
"First, we'll hard code some base cases:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "ba4aa4f0",
"metadata": {},
"outputs": [],
"source": [
"numerals = {\n",
" 1: \"one\",\n",
" 2: \"two\",\n",
" 3: \"three\",\n",
" 4: \"four\",\n",
" 5: \"five\",\n",
" 6: \"six\",\n",
" 7: \"seven\",\n",
" 8: \"eight\",\n",
" 9: \"nine\",\n",
" 10: \"ten\",\n",
" 11: \"eleven\",\n",
" 12: \"twelve\",\n",
" 13: \"thirteen\",\n",
" 14: \"fourteen\",\n",
" 15: \"fifteen\",\n",
" 16: \"sixteen\",\n",
" 17: \"seventeen\",\n",
" 18: \"eighteen\",\n",
" 19: \"nineteen\",\n",
" 20: \"twenty\",\n",
" 30: \"thirty\",\n",
" 40: \"forty\",\n",
" 50: \"fifty\",\n",
" 60: \"sixty\",\n",
" 70: \"seventy\",\n",
" 80: \"eighty\",\n",
" 90: \"ninety\",\n",
" }"
]
},
{
"cell_type": "markdown",
"id": "8b11e55b",
"metadata": {},
"source": [
"Obviously, if a number is explicitly given in this dictionary, that's its English numeral. For any other two digit number, the numeral is just the concatenation of the numerals for the tens place and the ones place (e.g. 37 is \"thirty\" and \"seven\").\n",
"\n",
"For three digit numbers, we write the numeral for the hundreds place, followed by \"hundred\" (e.g. 300 is \"three hundred\"). If it's an exact multiple of 100 (e.g. 100, 200, etc.) then that's it. Otherwise, we stick on \"and\" and follow the rules for a two digit number for the remaining digits.\n",
"\n",
"For this problem, all that's left is a special case for 1,000. Here's our function:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "18b8dacb",
"metadata": {},
"outputs": [],
"source": [
"def numeral(num):\n",
" if num == 1000:\n",
" return \"onethousand\"\n",
" elif num in numerals:\n",
" return numerals[num]\n",
" elif num % 100 == 0:\n",
" return numerals[num // 100] + \"hundred\"\n",
" elif num > 100:\n",
" return numerals[num // 100] + \"hundredand\" + numeral(num % 100)\n",
" \n",
" ones = num % 10\n",
" rest = num - ones\n",
" return numerals[rest] + numerals[ones]"
]
},
{
"cell_type": "markdown",
"id": "1e72e33b",
"metadata": {},
"source": [
"Now we can just use this to find all the numerals we need and sum their lengths."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "73b14cb1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"21124"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(len(numeral(n)) for n in range(1, 1001))"
]
},
{
"cell_type": "markdown",
"id": "417c208b",
"metadata": {},
"source": [
"## Relevent sequences\n",
"* Number of letters in British numeral: [A362123](https://oeis.org/A362123)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

179
problem0018.ipynb Normal file
View File

@ -0,0 +1,179 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b0604090",
"metadata": {},
"source": [
"# [Maximum Path Sum I](https://projecteuler.net/problem=18)\n",
"\n",
"As the problem notes, we could brute force every single path through this triangle, since there aren't that many, relatively speaking. But we're going to get a tougher version in [problem 67](https://projecteuler.net/problem=67), so if we make the effort now, we can kill two birds with one stone.\n",
"\n",
"First, let's encode the triangle as a list of lists:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "c3db04f0",
"metadata": {},
"outputs": [],
"source": [
" triangle = [[75],\n",
" [95, 64],\n",
" [17, 47, 82],\n",
" [18, 35, 87, 10],\n",
" [20, 4, 82, 47, 65],\n",
" [19, 1, 23, 75, 3, 34],\n",
" [88, 2, 77, 73, 7, 63, 67],\n",
" [99, 65, 4, 28, 6, 16, 70, 92],\n",
" [41, 41, 26, 56, 83, 40, 80, 70, 33],\n",
" [41, 48, 72, 33, 47, 32, 37, 16, 94, 29],\n",
" [53, 71, 44, 65, 25, 43, 91, 52, 97, 51, 14],\n",
" [70, 11, 33, 28, 77, 73, 17, 78, 39, 68, 17, 57],\n",
" [91, 71, 52, 38, 17, 14, 91, 43, 58, 50, 27, 29, 48],\n",
" [63, 66, 4, 68, 89, 53, 67, 30, 73, 16, 69, 87, 40, 31],\n",
" [4, 62, 98, 27, 23, 9, 70, 98, 73, 93, 38, 53, 60, 4, 23]]"
]
},
{
"cell_type": "markdown",
"id": "51f3aa5a",
"metadata": {},
"source": [
"There are a couple of important things to note about this problem. First, looking at the triangle, try to see how there are two \"sub-triangles\" below the top value, 75 (it's easier to see this on the original page rather than in the list-of-lists). One sub-triangle has a top value of 95, then the next row is 17 and 47, and the next row is 18, 35, and 87, and so on.\n",
"```\n",
" 95\n",
" 17 47\n",
" 18 35 87\n",
" ⋰ ⋮ ⋱\n",
"```\n",
"\n",
"The other sub-triangle has a top value of 64, and the next row is 47 and 82, and so on.\n",
"```\n",
" 64\n",
" 47 82\n",
" 35 87 10\n",
" ⋰ ⋮ ⋱\n",
"```\n",
"\n",
"If we think a little abstractly, the maximum path sum (MPS) through *the whole triangle* must equal the sum of the topmost value (75) with the larger of the MPSs through *each sub-triangle*. This fact holds for the lower layers as well: the MPS for the sub-triangle with top value 95 depends on the MPSs for the sub-triangles with top values 17 and 47, and the MPS for the sub-triangle with top value 64 depends on the MPSs for the sub-triangles with top values 47 and 82, and so on and so on.\n",
"\n",
"Because of this, the problem is said to have [optimal substructure](https://en.wikipedia.org/wiki/Optimal_substructure). In short, we find the MPS through the whole triangle by solving smaller and smaller subproblems, i.e. MPSs through sub-triangles. Eventually we will reach a base case: one of the values in the bottom-most layer of the triangle, where the maximum path sum of that \"sub-triangle\" is just the value itself.\n",
"\n",
"There's another important thing to consider: in trying to find the MPS through the 95-sub-triangle, we have to find the MPS through the 47-sub-triangle. Similarly, in trying to find the MPS through the 64-sub-triangle, we *also* have to find the MPS through the 47-sub-triangle. This reoccurrence of subproblems happens more and more frequently in the lower layers, as well. In other words, there are [overlapping subproblems](https://en.wikipedia.org/wiki/Overlapping_subproblems).\n",
"\n",
"When a problem has optimal substructure and overlapping subproblems, we can use [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming). We can employ either a top-down or bottom-up approach.\n",
"\n",
"## Top-down Method\n",
"This approach recusively finds the maximum path sums of each sub-triangle starting from the topmost value, similarly to what's described above. The `cache` decorator is used to memoize and avoid recomputing MPSs for any overlapping sub-triangles. This is crucial to the speed of a top-down approach; without memoization, we will recompute the solutions to overlapping subproblems way too often."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "778ba200",
"metadata": {},
"outputs": [],
"source": [
"from functools import cache\n",
"\n",
"@cache\n",
"def max_path_sum(tri):\n",
" if tri == tuple():\n",
" return 0\n",
"\n",
" left = tuple(row[:-1] for row in tri[1:])\n",
" right = tuple(row[1:] for row in tri[1:])\n",
" return tri[0][0] + max(max_path_sum(left), max_path_sum(right))"
]
},
{
"cell_type": "markdown",
"id": "dec8855a",
"metadata": {},
"source": [
"One slight wrinkle to this approach is that to use the `cache` decorator, the inputs to our function need to be [hashable](https://docs.python.org/3/glossary.html#term-hashable). Lists are not hashable, but tuples are, so we can convert our triangle to a tuple of tuples."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "789460a8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1074"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"max_path_sum(tuple(tuple(row) for row in triangle))"
]
},
{
"cell_type": "markdown",
"id": "4e267302",
"metadata": {},
"source": [
"## Bottom-up Method\n",
"Another approach is to instead find the MPSs of the very smallest sub-triangles first, then use those MPSs to find the MPSs in the next layer up. This doesn't require memoization, since by starting with the smallest subproblems, we ensure they are only computed once."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "23352679",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1074"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def max_path_sum(tri):\n",
" for i in reversed(range(0, len(tri) - 1)):\n",
" for j in range(0, len(tri[i])):\n",
" tri[i][j] += max(tri[i+1][j], tri[i+1][j+1])\n",
" \n",
" return tri[0][0]\n",
"\n",
"max_path_sum(triangle)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

109
problem0019.ipynb Normal file
View File

@ -0,0 +1,109 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "819ff42e",
"metadata": {},
"source": [
"# [Counting Sundays](https://projecteuler.net/problem=19)\n",
"\n",
"First, here's a simple function to give us the number of days in any given month and year."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "0f09cadc",
"metadata": {},
"outputs": [],
"source": [
"def days_in_month(month, year):\n",
" key = {1: 31,\n",
" 2: 28,\n",
" 3: 31,\n",
" 4: 30,\n",
" 5: 31,\n",
" 6: 30,\n",
" 7: 31,\n",
" 8: 31,\n",
" 9: 30,\n",
" 10: 31,\n",
" 11: 30,\n",
" 12: 31}\n",
" \n",
" if year % 400 == 0 or (year % 4 == 0 and year % 100 != 0):\n",
" key[2] = 29\n",
" \n",
" return key[month]"
]
},
{
"cell_type": "markdown",
"id": "1537a949",
"metadata": {},
"source": [
"Let's talk briefly about the days of the week. If someone asked you on a Monday what day it will be in eight days, you could naively count up (one day from now is Tuesday, two days from now is Wednesday, etc.), but it's quicker to observe that since the days of the week repeat every seven days, seven days from now will also be a Monday, and therefore eight days later is a Tuesday. Likewise, if someone wanted to know what day is 17 days from now, you know 14 days from now is also a Monday, so 17 (14+3) days later would be Thursday (3 days after Monday).\n",
"\n",
"Now, let's reframe this concept mathematically. Let's assign a number to every day of the week: Sunday is 0, Monday is 1, Tuesday is 2, and so on. Using [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic), we can add the number of days later to our day-number, then compute the remainder modulo 7 to find the day of our new date. Restating what we found above, if today is a Monday (1), we just add 17 (to get 18) and compute the remainder modulo 7 to see that 17 days from now will be Thursday (4). As a formula: if the day-number is $s$, the day it will be $n$ days later is $(s + n) \\bmod 7$.\n",
"\n",
"Knowing this, to find the day each month started on, we can just iterate through every month from 1900 through 2000, and each time add the length of the month to the day the last month started on, compute the modulus, and check if the result is 0, indicating that month started on a Sunday. (Note that the question gives us the starting day of 1900, but only asked about months from 1901 through 2000, so we'll take care to not add any months from 1900 that fell on a Sunday.)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "1c8f38fa",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"171\n"
]
}
],
"source": [
"total = 0\n",
"day = 1\n",
"for year in range(1900, 2001):\n",
" for month in range(1, 13):\n",
" day += days_in_month(month, year)\n",
" day %= 7\n",
" if day == 0 and year >= 1901:\n",
" total += 1\n",
"\n",
"print(total)"
]
},
{
"cell_type": "markdown",
"id": "83b6e18e",
"metadata": {},
"source": [
"One last note, just for fun: did you know you can learn how to calculate what day of the week any date falls on [in your head](https://en.wikipedia.org/wiki/Doomsday_rule)?"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

65
problem0020.ipynb Normal file
View File

@ -0,0 +1,65 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "c1d94bd8",
"metadata": {},
"source": [
"# [Factorial Digit Sum](https://projecteuler.net/problem=20)\n",
"\n",
"Piece of cake for SageMath."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "894e7489",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"648"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(map(int, str(factorial(100))))"
]
},
{
"cell_type": "markdown",
"id": "6dab9653",
"metadata": {},
"source": [
"## Relevant sequences\n",
"* Sum of digits of $n!$: [A004152](https://oeis.org/A004152)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "SageMath 9.5",
"language": "sage",
"name": "sagemath"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}