The max recursion for the coefficient of a polynomial
Question:
Recently, I came across an interesting counting problem on YouTube posed by 3Blue1Brown-OlympiadLevelCounting. The problem is to find the number of subsets of the set {1, 2, …, 2000} whose elements’ sum is divisible by five. Grant Sanderson presented a beautiful solution, but he also discussed how cumbersome the algorithm could become if implemented on computers to solve the problem efficiently.
However, there is beauty in using integer partitions to solve this problem, thanks to the work of the Euler. The solution is the sum of the distinct partitions of all multiples of 5 less than or equal to 2000 something similar to what I had posted on MathstackExchange years ago, where the comment by use "dxiv" was the key to this code.
Undoubtedly, Grant Sanderson’s solution is elegant as it does not require a computer and can be found without lengthy calculations.
My code is:
import numpy as np
def p(k):
if k == 1:
return np.array([1, 1])
else:
q = p(k - 1)
return add(q, np.append(np.zeros(k, int), q))
def add(u, v):
u = np.append(u, np.zeros(len(v) - len(u), int))
return np.add(u, v)
def test(n):
a = 1 << n
b = 1 << (n//5)
b *= 4
return (a+b)//5
n = 1000
generating_function = sum(p(n)[0:-1:5])+1
grant_sanderson = test(n)
print(generating_function)
print(grant_sanderson)
print(generating_function == grant_sanderson)
My difficulty is when n
is a large number. Also, these numbers are too large that I have to use gmpy2
this is a graph that shows that arrays are symmetric about n//2
:
Traceback (most recent call last):
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2main.py", line 15, in <module>
generating_function = sum(p(n)[0:-1:5])+1
^^^^
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
q = p(k - 1)
^^^^^^^^
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
q = p(k - 1)
^^^^^^^^
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
q = p(k - 1)
^^^^^^^^
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
This code can show how things quickly go out of hands:
import gmpy2
def q(n):
Q = [gmpy2.mpz(0)] * (n+1)
Q[0] = gmpy2.mpz(1)
for k in range(1, n):
for i in range(n, k-1, -1):
Q[i] += Q[i-k]
return Q[n] + 1
Answers:
An alternative method would be to count combinations of sums that reach each of the 5 modulo values (0,1,2,3,4). Hold these counts in a list and increase them progressively. At the end, take the count for the zero modulo to get the number of sets that add up to a multiple of 5:
def sumDiv5Count(numbers):
modCounts = [0]*5
for n in numbers:
modCounts = [c+modCounts[(i-n)%5] for i,c in enumerate(modCounts)]
modCounts[n%5] += 1
return modCounts[0]
Each number’s modulo 5 combines with the 5 possible moduli to add more ways to produce 5 distinct resulting modulo values.
For example 13%5 -> 3,
-
ways to produce 0 are added to ways to produce 0+3 –> slot 3
-
ways to produce 1 are added to ways to produce 1+3 –> slot 4
-
ways to produce 2 are added to ways to produce 2+3 –> slot 0 (2+3)%5 = 0
-
ways to produce 3 are added to ways to produce 3+3 –> slot 1 (3+3)%5 = 1
-
ways to produce 4 are added to ways to produce 4+3 –> slot 2 (4+3)%5 = 2
-
and there is one more way to produce 3 by itself
output:
print(sumDiv5Count(range(1,10)))
# 103
print(sumDiv5Count(range(1,50)))
# 112589990684671
print(sumDiv5Count(range(1,100)))
# 126765060022822940149670739967
print(sumDiv5Count(range(1,500)))
# 327339060789614187001318969682759915221664204604306478948329136809613379640467455488327009232590415715088668412756007101428785894679831065931534041087
print(sumDiv5Count(range(1,1000)))
# 1071508607186267320948425049060001810561404811705533607443750388370351051124936122493198378815695858127594672917553146825187145285692314043598457757469857480393456777482423098542107460506237114187795418215304647498358194126739876755916554395250481509160715757985439053702508024174143636196837700927487
print(sumDiv5Count(range(1,2001)))
# 22962613905485090484656664023553639680446354041773904009552854736515325227847406277133189726330125398368919292779749255468942379217261106628518627123333063707825997829062456000137755829648008974285785398012697248956323092729277672789463405208093270794180999311632479761788925921124662329907232844394066536268833781796891701120475896961582811780186955300085800543341325166104401626447256258352253576663441319799079283625404355971680808431970636650308177886780418384110991556717934409897816293912852988275811422719154702569434391547265221166310540389294622648560061463880851178273858239474974548427800575
One nice thing about this approach is that it could easily be turned into a generator that yields the count progressively as it goes through the numbers. It is also not dependent on the numbers being in a sequence or even distinct. Performance is good (a few milliseconds) given that it’s an O(n) process.
This doesn’t throw recursion error!
Changes made to the original code:
sys.setrecursionlimit(2500)
- Don’t know why, but
gmpy2
was throwing wrong numbers. so used decimal.Decimal
instead
from decimal import Decimal, getcontext
import sys
sys.setrecursionlimit(2500)
def p(k):
if k == 1:
return [Decimal(1), Decimal(1)]
else:
q = p(k - 1)
return add(q, [Decimal(0)] * k + q)
def add(u, v):
u = u + [Decimal(0)] * (len(v) - len(u))
return [x + y for x, y in zip(u, v)]
def test(n):
getcontext().prec = n+10
a = Decimal(1 << n)
b = Decimal(1 << (n//5))
b *= Decimal(4)
result = (a+b)/Decimal(5)
return Decimal(result)
n = 2000
generating_function = sum(p(n)[0:-1:5])+1
print(generating_function)
Recently, I came across an interesting counting problem on YouTube posed by 3Blue1Brown-OlympiadLevelCounting. The problem is to find the number of subsets of the set {1, 2, …, 2000} whose elements’ sum is divisible by five. Grant Sanderson presented a beautiful solution, but he also discussed how cumbersome the algorithm could become if implemented on computers to solve the problem efficiently.
However, there is beauty in using integer partitions to solve this problem, thanks to the work of the Euler. The solution is the sum of the distinct partitions of all multiples of 5 less than or equal to 2000 something similar to what I had posted on MathstackExchange years ago, where the comment by use "dxiv" was the key to this code.
Undoubtedly, Grant Sanderson’s solution is elegant as it does not require a computer and can be found without lengthy calculations.
My code is:
import numpy as np
def p(k):
if k == 1:
return np.array([1, 1])
else:
q = p(k - 1)
return add(q, np.append(np.zeros(k, int), q))
def add(u, v):
u = np.append(u, np.zeros(len(v) - len(u), int))
return np.add(u, v)
def test(n):
a = 1 << n
b = 1 << (n//5)
b *= 4
return (a+b)//5
n = 1000
generating_function = sum(p(n)[0:-1:5])+1
grant_sanderson = test(n)
print(generating_function)
print(grant_sanderson)
print(generating_function == grant_sanderson)
My difficulty is when n
is a large number. Also, these numbers are too large that I have to use gmpy2
this is a graph that shows that arrays are symmetric about n//2
:
Traceback (most recent call last):
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2main.py", line 15, in <module>
generating_function = sum(p(n)[0:-1:5])+1
^^^^
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
q = p(k - 1)
^^^^^^^^
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
q = p(k - 1)
^^^^^^^^
File "c:UserspatilOneDriveDocumentsGitHubTheorie-der-ZahlenDistinct partitionsdistinctParitions-gmp2generatingfunction.py", line 8, in p
q = p(k - 1)
^^^^^^^^
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
This code can show how things quickly go out of hands:
import gmpy2
def q(n):
Q = [gmpy2.mpz(0)] * (n+1)
Q[0] = gmpy2.mpz(1)
for k in range(1, n):
for i in range(n, k-1, -1):
Q[i] += Q[i-k]
return Q[n] + 1
An alternative method would be to count combinations of sums that reach each of the 5 modulo values (0,1,2,3,4). Hold these counts in a list and increase them progressively. At the end, take the count for the zero modulo to get the number of sets that add up to a multiple of 5:
def sumDiv5Count(numbers):
modCounts = [0]*5
for n in numbers:
modCounts = [c+modCounts[(i-n)%5] for i,c in enumerate(modCounts)]
modCounts[n%5] += 1
return modCounts[0]
Each number’s modulo 5 combines with the 5 possible moduli to add more ways to produce 5 distinct resulting modulo values.
For example 13%5 -> 3,
-
ways to produce 0 are added to ways to produce 0+3 –> slot 3
-
ways to produce 1 are added to ways to produce 1+3 –> slot 4
-
ways to produce 2 are added to ways to produce 2+3 –> slot 0 (2+3)%5 = 0
-
ways to produce 3 are added to ways to produce 3+3 –> slot 1 (3+3)%5 = 1
-
ways to produce 4 are added to ways to produce 4+3 –> slot 2 (4+3)%5 = 2
-
and there is one more way to produce 3 by itself
output:
print(sumDiv5Count(range(1,10)))
# 103
print(sumDiv5Count(range(1,50)))
# 112589990684671
print(sumDiv5Count(range(1,100)))
# 126765060022822940149670739967
print(sumDiv5Count(range(1,500)))
# 327339060789614187001318969682759915221664204604306478948329136809613379640467455488327009232590415715088668412756007101428785894679831065931534041087
print(sumDiv5Count(range(1,1000)))
# 1071508607186267320948425049060001810561404811705533607443750388370351051124936122493198378815695858127594672917553146825187145285692314043598457757469857480393456777482423098542107460506237114187795418215304647498358194126739876755916554395250481509160715757985439053702508024174143636196837700927487
print(sumDiv5Count(range(1,2001)))
# 22962613905485090484656664023553639680446354041773904009552854736515325227847406277133189726330125398368919292779749255468942379217261106628518627123333063707825997829062456000137755829648008974285785398012697248956323092729277672789463405208093270794180999311632479761788925921124662329907232844394066536268833781796891701120475896961582811780186955300085800543341325166104401626447256258352253576663441319799079283625404355971680808431970636650308177886780418384110991556717934409897816293912852988275811422719154702569434391547265221166310540389294622648560061463880851178273858239474974548427800575
One nice thing about this approach is that it could easily be turned into a generator that yields the count progressively as it goes through the numbers. It is also not dependent on the numbers being in a sequence or even distinct. Performance is good (a few milliseconds) given that it’s an O(n) process.
This doesn’t throw recursion error!
Changes made to the original code:
sys.setrecursionlimit(2500)
- Don’t know why, but
gmpy2
was throwing wrong numbers. so useddecimal.Decimal
instead
from decimal import Decimal, getcontext
import sys
sys.setrecursionlimit(2500)
def p(k):
if k == 1:
return [Decimal(1), Decimal(1)]
else:
q = p(k - 1)
return add(q, [Decimal(0)] * k + q)
def add(u, v):
u = u + [Decimal(0)] * (len(v) - len(u))
return [x + y for x, y in zip(u, v)]
def test(n):
getcontext().prec = n+10
a = Decimal(1 << n)
b = Decimal(1 << (n//5))
b *= Decimal(4)
result = (a+b)/Decimal(5)
return Decimal(result)
n = 2000
generating_function = sum(p(n)[0:-1:5])+1
print(generating_function)