Skip to content

Commit 33a26e3

Browse files
committed
split into more posts
1 parent 6689c07 commit 33a26e3

77 files changed

Lines changed: 8990 additions & 2154 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

404.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,8 @@ <h2>Latest</h2>
570570

571571
<li><a href="/post/">Posts</a></li>
572572

573+
<li><a href="/post/binomial-modulo-prime/">Binomial Coefficients Modulo a Prime: Fermat&#39;s Theorem and the Non-Adjacent Selection Problem</a></li>
574+
573575
<li><a href="/post/efficient-implementation-non-adjacent-selection/">Efficient Implementation of the Non-Adjacent Selection Formula</a></li>
574576

575577
<li><a href="/post/two-var-recursive-func/">Cracking Multivariate Recursive Equations Using Generating Functions</a></li>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
+++
2+
date = "2017-07-08T07:29:43Z"
3+
highlight = true
4+
math = true
5+
tags = ["math", "python", "binomial", "combinatorics", "programming", "modular arithmetic", "competitive programming"]
6+
title = "Binomial Coefficients Modulo a Prime: Fermat's Theorem and the Non-Adjacent Selection Problem"
7+
8+
[header]
9+
caption = ""
10+
image = ""
11+
12+
+++
13+
14+
In the [previous post][efficient-impl], we implemented the closed form $F_{n,m} = \binom{n-m+1}{m}$ using Python's `math.factorial`, and with `scipy` and `sympy`. Here we cover the common competitive-programming case: computing the answer **modulo a large prime** $M$ (e.g. $M = 10^9+7$).
15+
16+
## Why modulo?
17+
18+
In counting problems, the result can be huge even for moderate input. Often the problem asks for the answer modulo a big prime so that it fits in a standard integer type. We could compute the full number and then take the remainder, but that forces expensive long-integer arithmetic. Computing **everything** modulo $M$ from the start is much faster.
19+
20+
## From binomials to modular inverses
21+
22+
We have $F_{n,m} = \binom{n-m+1}{m} = \frac{(n-m+1)!}{m!\,(n-2m+1)!}$. To compute this mod $M$, we need factorials mod $M$ and division mod $M$. Division mod $M$ is multiplication by the **modular inverse**: for prime $M$ and $0 < x < M$, the inverse of $x$ is $x^{M-2} \bmod M$ by [Fermat's little theorem](https://en.wikipedia.org/wiki/Fermat%27s_little_theorem). In Python we can use `pow(x, M - 2, M)`.
23+
24+
## Implementation
25+
26+
```python
27+
import functools
28+
29+
M = 10**9 + 7
30+
31+
def f_binom_mod(n, m):
32+
assert n >= 0 and m >= 0
33+
34+
if n + 1 < 2*m:
35+
return 0
36+
37+
return binom_mod(n - m + 1, m)
38+
39+
def binom_mod(n, m):
40+
assert 0 <= m <= n
41+
42+
return ((fact_mod(n) * inv_mod(fact_mod(m))) % M * inv_mod(fact_mod(n - m))) % M
43+
44+
@functools.lru_cache(maxsize=None)
45+
def fact_mod(m):
46+
if m <= 1:
47+
return 1
48+
49+
return (m * fact_mod(m - 1)) % M
50+
51+
def inv_mod(x):
52+
return pow(x, M - 2, M)
53+
```
54+
55+
All operations stay in the ring of integers mod $M$. The only non-obvious part is modular division: we replace division by $d$ with multiplication by `inv_mod(d)` using Fermat's little theorem.
56+
57+
## Benchmarks
58+
59+
Compared to computing the full binomial and then taking the remainder, the modular version avoids long arithmetic and is much faster:
60+
61+
```python
62+
fact_mod(10000) # for caching factorials
63+
64+
funcs = [f_binom_mod, f_binom, f_sci, f_sym]
65+
66+
test(10000, 1000, funcs)
67+
test(10000, 2000, funcs)
68+
test(10000, 3000, funcs)
69+
```
70+
71+
Example output:
72+
73+
```
74+
f(10000,1000): 450169549
75+
f_binom_mod: 0.0000 sec, x 1.00
76+
f_binom: 0.0073 sec, x 337.60
77+
f_sci: 0.0011 sec, x 49.33
78+
f_sym: 0.0076 sec, x 353.22
79+
80+
f(10000,2000): 75198348
81+
f_binom_mod: 0.0000 sec, x 1.00
82+
f_binom: 0.0063 sec, x 368.94
83+
f_sci: 0.0026 sec, x 153.33
84+
f_sym: 0.0053 sec, x 308.93
85+
86+
f(10000,3000): 679286557
87+
f_binom_mod: 0.0000 sec, x 1.00
88+
f_binom: 0.0060 sec, x 361.12
89+
f_sci: 0.0056 sec, x 338.13
90+
f_sym: 0.0053 sec, x 319.02
91+
```
92+
93+
The same pattern—factorials mod $M$ plus Fermat-based inverses—works for any combinatorial formula that can be written in terms of factorials and binomials modulo a prime.
94+
95+
[efficient-impl]: /post/efficient-implementation-non-adjacent-selection/

content/post/efficient-implementation-non-adjacent-selection.md

Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ In the [previous post][two-var-recursive], we derived the closed form for the no
1515

1616
$$ F_{n, m} = {n - m + 1 \choose m} $$
1717

18-
Now we discuss how to implement this efficiently in Python—from a simple factorial-based solution to library implementations and modular arithmetic for competitive programming.
18+
Now we discuss how to implement this efficiently in Python—from a simple factorial-based solution to library implementations. For the common case of computing the answer **modulo a large prime** (e.g. in competitive programming), see the [next post][binom-mod].
1919

2020
## Fast Solutions Based on Binomials
2121

@@ -142,86 +142,8 @@ You can play with running tests on different $n$ and $m$.
142142
What I saw that actually there is no clear winner between the last 3 implementations.
143143
Probably, the most of the time is spent on the long arithmetic computation.
144144

145-
## Modular Arithmetics
146-
147-
In questions where it is required to count some objects, not rarely the answer might be very big even on very small input.
148-
In such case, typically it is asked to print the answer modulo some big prime integer, let's say, $M=1000^3+7$.
149-
Since Python has built-in long arithmetics, we can apply modulo on the final result,
150-
but executing the entire algorithm with long arithmetics while knowing that only small part of it is really important is very costly,
151-
and of course, not that efficient.
152-
153-
Let's look, briefly, at very simple change we can do for `f_binom` function that will speed up the computation significantly:
154-
155-
```python
156-
import functools
157-
158-
M = 10**9 + 7
159-
160-
def f_binom_mod(n, m):
161-
assert n >= 0 and m >= 0
162-
163-
if n + 1 < 2*m:
164-
return 0
165-
166-
return binom_mod(n - m + 1, m)
167-
168-
def binom_mod(n, m):
169-
assert 0 <= m <= n
170-
171-
return ((fact_mod(n) * inv_mod(fact_mod(m))) % M * inv_mod(fact_mod(n - m))) % M
172-
173-
@functools.lru_cache(maxsize=None)
174-
def fact_mod(m):
175-
if m <= 1:
176-
return 1
177-
178-
return (m * fact_mod(m - 1)) % M
179-
180-
def inv_mod(x):
181-
return pow(x, M - 2, M)
182-
```
183-
184-
As we can see, all the operations are computed modulo $M$.
185-
The function `fact_mod` is recursive but uses Memoization.
186-
The most tricky part is how to implement modular-division.
187-
From [Fermat's little theorem](https://en.wikipedia.org/wiki/Fermat%27s_little_theorem),
188-
we know that if $M$ is prime and $0 < x < M$, then $x^{-1} \equiv x^{M-2} \pmod M$.
189-
This allows to compute the multiplicative inverse of $x$ using the Python's built-in function
190-
[pow](https://docs.python.org/3/library/functions.html#pow).
191-
192-
Let's test the new approach against other implementations:
193-
194-
```python
195-
fact_mod(10000) # for caching factorials
196-
197-
funcs = [f_binom_mod, f_binom, f_sci, f_sym]
198-
199-
test(10000, 1000, funcs)
200-
test(10000, 2000, funcs)
201-
test(10000, 3000, funcs)
202-
```
203-
204-
It is not a surprise that taking the benefits of modular computations results in the huge speedup in running-time:
205-
206-
```
207-
f(10000,1000): 450169549
208-
f_binom_mod: 0.0000 sec, x 1.00
209-
f_binom: 0.0073 sec, x 337.60
210-
f_sci: 0.0011 sec, x 49.33
211-
f_sym: 0.0076 sec, x 353.22
212-
213-
f(10000,2000): 75198348
214-
f_binom_mod: 0.0000 sec, x 1.00
215-
f_binom: 0.0063 sec, x 368.94
216-
f_sci: 0.0026 sec, x 153.33
217-
f_sym: 0.0053 sec, x 308.93
218-
219-
f(10000,3000): 679286557
220-
f_binom_mod: 0.0000 sec, x 1.00
221-
f_binom: 0.0060 sec, x 361.12
222-
f_sci: 0.0056 sec, x 338.13
223-
f_sym: 0.0053 sec, x 319.02
224-
```
145+
When the problem asks for the answer **modulo a large prime** (e.g. $10^9+7$), computing everything mod $M$ from the start is much faster than using long integers. We cover that in a [separate post][binom-mod]: binomial coefficients modulo a prime using Fermat's little theorem.
225146

226147
[intro-to-dp]: /post/intro-to-dp/
227148
[two-var-recursive]: /post/two-var-recursive-func/
149+
[binom-mod]: /post/binomial-modulo-prime/

content/post/two-var-recursive-func.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ Which actually equals to
8181

8282
$$ F\_{n, m} = {n - m + 1 \choose m} $$
8383

84-
In the [next post][efficient-impl], we discuss how to implement this closed form efficiently in Python—from a simple factorial-based solution to library implementations and modular arithmetic for competitive programming.
84+
In the [next post][efficient-impl], we implement this closed form in Python (factorial-based and with scipy/sympy). For computing the answer modulo a large prime, see [Binomial Coefficients Modulo a Prime][binom-mod].
8585

8686
[intro-to-dp]: /post/intro-to-dp/
8787
[gen-func-art]: /post/gen-func-art/
8888
[efficient-impl]: /post/efficient-implementation-non-adjacent-selection/
89+
[binom-mod]: /post/binomial-modulo-prime/
8990

index.html

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,95 @@ <h1 class="mb-0">Recent Posts</h1>
970970

971971

972972

973+
974+
<div class="media stream-item view-compact">
975+
<div class="media-body">
976+
977+
<div class="section-subheading article-title mb-0 mt-0">
978+
<a href="/post/binomial-modulo-prime/" >Binomial Coefficients Modulo a Prime: Fermat&#39;s Theorem and the Non-Adjacent Selection Problem</a>
979+
</div>
980+
981+
982+
<a href="/post/binomial-modulo-prime/" class="summary-link">
983+
<div class="article-style">
984+
<p>In the <a href="/post/efficient-implementation-non-adjacent-selection/">previous post</a>, we implemented the closed form $F_{n,m} = \binom{n-m+1}{m}$ using Python&rsquo;s <code>math.factorial</code>, and with <code>scipy</code> and <code>sympy</code>. Here we cover the common competitive-programming case: computing the answer <strong>modulo a large prime</strong> $M$ (e.g. $M = 10^9+7$).</p>
985+
<h2 id="why-modulo">Why modulo?</h2>
986+
<p>In counting problems, the result can be huge even for moderate input. Often the problem asks for the answer modulo a big prime so that it fits in a standard integer type. We could compute the full number and then take the remainder, but that forces expensive long-integer arithmetic. Computing <strong>everything</strong> modulo $M$ from the start is much faster.</p>
987+
</div>
988+
</a>
989+
990+
991+
<div class="stream-meta article-metadata">
992+
993+
994+
995+
996+
997+
998+
999+
<div class="article-metadata">
1000+
1001+
1002+
1003+
1004+
1005+
<span class="article-date">
1006+
1007+
1008+
1009+
1010+
Jul 8, 2017
1011+
</span>
1012+
1013+
1014+
1015+
1016+
1017+
<span class="middot-divider"></span>
1018+
<span class="article-reading-time">
1019+
2 min read
1020+
</span>
1021+
1022+
1023+
1024+
1025+
1026+
1027+
1028+
1029+
1030+
1031+
</div>
1032+
1033+
1034+
</div>
1035+
1036+
1037+
1038+
</div>
1039+
<div class="ml-3">
1040+
1041+
1042+
</div>
1043+
</div>
1044+
1045+
1046+
1047+
1048+
1049+
1050+
1051+
1052+
1053+
1054+
1055+
1056+
1057+
1058+
1059+
1060+
1061+
9731062

9741063
<div class="media stream-item view-compact">
9751064
<div class="media-body">
@@ -983,26 +1072,9 @@ <h1 class="mb-0">Recent Posts</h1>
9831072
<div class="article-style">
9841073
<p>In the <a href="/post/two-var-recursive-func/">previous post</a>, we derived the closed form for the non-adjacent selection problem:</p>
9851074
<p>$$ F_{n, m} = {n - m + 1 \choose m} $$</p>
986-
<p>Now we discuss how to implement this efficiently in Python—from a simple factorial-based solution to library implementations and modular arithmetic for competitive programming.</p>
1075+
<p>Now we discuss how to implement this efficiently in Python—from a simple factorial-based solution to library implementations. For the common case of computing the answer <strong>modulo a large prime</strong> (e.g. in competitive programming), see the <a href="/post/binomial-modulo-prime/">next post</a>.</p>
9871076
<h2 id="fast-solutions-based-on-binomials">Fast Solutions Based on Binomials</h2>
9881077
<p>We can reflect the closed form in very trivial Python code:</p>
989-
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Python" data-lang="Python"><span style="display:flex;"><span><span style="color:#f92672">import</span> math
990-
</span></span><span style="display:flex;"><span>
991-
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">f_binom</span>(n, m):
992-
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">assert</span> n <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">and</span> m <span style="color:#f92672">&gt;=</span> <span style="color:#ae81ff">0</span>
993-
</span></span><span style="display:flex;"><span>
994-
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> n <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">2</span><span style="color:#f92672">*</span>m:
995-
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span>
996-
</span></span><span style="display:flex;"><span>
997-
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> binom(n <span style="color:#f92672">-</span> m <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, m)
998-
</span></span><span style="display:flex;"><span>
999-
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">binom</span>(n, m):
1000-
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">assert</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&lt;=</span> m <span style="color:#f92672">&lt;=</span> n
1001-
</span></span><span style="display:flex;"><span>
1002-
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> math<span style="color:#f92672">.</span>factorial(n) <span style="color:#f92672">//</span> math<span style="color:#f92672">.</span>factorial(m) <span style="color:#f92672">//</span> math<span style="color:#f92672">.</span>factorial(n <span style="color:#f92672">-</span> m)
1003-
</span></span></code></pre></div><p>This implementation overperforms significantly the initial DP and memoization solutions from <a href="/post/intro-to-dp/">Introduction to Dynamic Programming and Memoization</a>.
1004-
A naive implementation of <code>math.factorial()</code> might make $n$ multiplications.
1005-
This could still be faster than doing $\Theta(n)$ additions in DP approach.</p>
10061078
</div>
10071079
</a>
10081080

@@ -1035,7 +1107,7 @@ <h2 id="fast-solutions-based-on-binomials">Fast Solutions Based on Binomials</h2
10351107

10361108
<span class="middot-divider"></span>
10371109
<span class="article-reading-time">
1038-
6 min read
1110+
4 min read
10391111
</span>
10401112

10411113

0 commit comments

Comments
 (0)