Skip to content

Commit cb7b372

Browse files
committed
Solving a Three-Variable Recursion via Generating Functions
1 parent daeeacc commit cb7b372

48 files changed

Lines changed: 6111 additions & 240 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
@@ -568,6 +568,8 @@ <h1>Page not found</h1>
568568
<h2>Latest</h2>
569569
<ul>
570570

571+
<li><a href="/post/three-var-recursive/">Solving a Three-Variable Recursion via Generating Functions</a></li>
572+
571573
<li><a href="/post/">Posts</a></li>
572574

573575
<li><a href="/post/perfect-distribution/">Perfect Distribution: GCD in Disguise</a></li>
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
+++
2+
date = "2018-04-10T03:14:00Z"
3+
math = true
4+
highlight = true
5+
tags = ["math", "python", "generating function", "recursion", "combinatorics", "dynamic programming", "multivariate function"]
6+
title = "Solving a Three-Variable Recursion via Generating Functions"
7+
draft = false
8+
9+
[header]
10+
image = ""
11+
caption = ""
12+
+++
13+
14+
This post extends the generating-function technique from the [two-variable recursion][two-var-recursive-func] to a three-variable case. I originally wrote this as an answer to a [Math Stack Exchange question][math-se]; here it is adapted for the blog with clearer exposition and code.
15+
16+
## The Problem
17+
18+
We want to solve the recurrence
19+
$$
20+
\begin{aligned}
21+
$$a(m,n,k) &= 2a(m-1,n-1,k-1) + a(m-1,n-1,k) \\
22+
&+ a(m-1,n,k-1) + a(m,n-1,k-1)$$
23+
\end{aligned}
24+
$$
25+
where $m$, $n$, $k$ are nonnegative integers, with boundary conditions:
26+
27+
- $a(0,0,0) = a(1,0,0) = a(0,1,0) = a(0,0,1) = 1$
28+
- $a(m,0,0) = a(0,m,0) = a(0,0,m) = 0$ for any $m > 1$
29+
- $a(m,n,k)$ is symmetric in $m$,$n$,$k$
30+
31+
A subtlety: $a(0,1,1)$ is not defined by the recurrence alone, since it would require values like $a(-1,0,0)$. We take $a(m,n,k) = 0$ whenever any argument is negative.
32+
33+
## The Generating Function
34+
35+
Define
36+
37+
$$\Phi(x,y,z) = \sum_{m,n,k \geq 0} a(m,n,k) \cdot x^m y^n z^k$$
38+
39+
Using the initial values above, we can define $a(m,n,k)$ as follows:
40+
41+
$$
42+
\begin{aligned}
43+
a(m, n, k) &= 2a(m-1, n-1, k-1) + a(m-1, n-1, k) \\
44+
&+ a(m-1, n, k-1) + a(m, n-1, k-1) \\
45+
&+ [m=n=k=0] + [m=n=0 \wedge k=1] + [m=k=0 \wedge n=1] + [n=k=0 \wedge m=1]
46+
\end{aligned}
47+
$$
48+
49+
believe there are still some initial conditions missing, since for example $a(0,1,1)$ is not well defined. Computing its value will result in negative arguments: $a(0,1,1) = 2a(-1, 0, 0) + a(-1, 0, 1) + a(-1, 1, 0) + a(0, 0, 0) = 2a(-1, 0, 0) + 2a(-1, 0, 1) + 1$.
50+
51+
Adding the extra condition that $a(m,n,k)=0$ for any negative argument(s) solves the issue.
52+
53+
Let's move forward and substitute the definition of $a(m,n,k)$ into the generating function:
54+
55+
$$
56+
\begin{aligned}
57+
\Phi(x,y,z)
58+
&=\sum_{m,n,k}a(m,n,k) \cdot x^m y^n z^k \\
59+
&= 2\sum_{m,n,k}a(m,n,k) \cdot x^{m+1} y^{n+1} z^{k+1} + \sum_{m,n,k}a(m,n,k) \cdot x^{m+1} y^{n+1} z^k + \sum_{m,n,k}a(m,n,k) \cdot x^{m+1} y^n z^{k+1}+\sum_{m,n,k}a(m,n,k) \cdot x^m y^{n+1} z^{k+1} + 1 + x + y + z \\
60+
&= 2\Phi(x,y,z) \cdot x y z + \Phi(x,y,z)\cdot x y + \Phi(x,y,z) \cdot x z + \Phi(x,y,z) \cdot y z + 1 + x + y + z \\
61+
&= \Phi(x,y,z)\left(2 x y z + x y + x z + y z\right) + 1 + x + y + z
62+
\end{aligned}
63+
$$
64+
65+
66+
Substituting the recurrence and collecting terms, we get
67+
68+
$$
69+
\begin{aligned}
70+
\Phi(x,y,z)
71+
&= \sum_{m,n,k} a(m,n,k) \cdot x^m y^n z^k \\
72+
&= 2\Phi \cdot xyz + \Phi \cdot xy + \Phi \cdot xz + \Phi \cdot yz + 1 + x + y + z
73+
\end{aligned}
74+
$$
75+
76+
where the boundary terms $1 + x + y + z$ come from $a(0,0,0)$, $a(1,0,0)$, $a(0,1,0)$, $a(0,0,1)$. Solving for $\Phi$:
77+
78+
$$\Phi(x,y,z) = \frac{1 + x + y + z}{1 - 2xyz - xy - xz - yz}$$
79+
80+
## From Generating Function to Closed Form
81+
82+
Using $\frac{1}{1-\rho} = \sum_{i \geq 0} \rho^i$ and the multinomial expansion
83+
84+
$$(x_1+x_2+x_3+x_4)^N = \sum_{k_1+k_2+k_3+k_4=N} \binom{N}{k_1,k_2,k_3,k_4} x_1^{k_1} x_2^{k_2} x_3^{k_3} x_4^{k_4}$$
85+
86+
with $\binom{N}{k_1,k_2,k_3,k_4} = \frac{N!}{k_1!\cdot k_2!\cdot k_3!\cdot k_4!}$, we expand the denominator. Let $\rho = 2xyz + xy + xz + yz$. Then
87+
88+
$$
89+
\Phi = (1+x+y+z) \sum_{N \geq 0} \rho^N
90+
$$
91+
92+
93+
$$
94+
\begin{aligned}
95+
&= \frac{1 + x + y + z}{1-2 x y z - x y - x z - y z} \\
96+
&= (1 + x + y + z) \sum_{N}(2 x y z + x y + x z + y z)^N \\
97+
&= (1 + x + y + z) \sum_{k_1+k_2+k_3+k_4=N} \binom{N} {k_1,k_2,k_3,k_4} (2 x y z)^{k_1} \cdot (x y)^{k_2} \cdot (x z)^{k_3} \cdot (y z)^{k_4} \\
98+
&= (1 + x + y + z) \sum_{k_1+k_2+k_3+k_4=N} \binom{N} {k_1,k_2,k_3,k_4} 2^{k_1} x^{k_1+k_2+k_3} y^{k_1+k_2+k_4} z^{k_1+k_3+k_4} \\
99+
&= (1 + x + y + z) \sum_{k_1+k_2+k_3\leq N} \binom{N} {k_1,k_2,k_3,N-k_1-k_2-k_3} 2^{k_1} x^{k_1+k_2+k_3} y^{N-k_3} z^{N-k_2}
100+
\end{aligned}
101+
$$
102+
103+
104+
Writing $\rho^N$ with terms $(2xyz)^{k_1}(xy)^{k_2}(xz)^{k_3}(yz)^{k_4}$ where $k_1+k_2+k_3+k_4 = N$, and extracting the coefficient of $x^m y^n z^k$, yields the closed form:
105+
106+
$$
107+
\begin{aligned}
108+
a(m,n,k)
109+
&= \sum_{\max(m,n,k) \leq N \leq \frac{m+n+k}{2}}
110+
\binom{N}{m+n+k-2N, N-m, N-n, N-k} 2^{m+n+k-2N} \\
111+
&\quad + \text{three similar sums from the } 1, x, y, z \text{ terms}
112+
\end{aligned}
113+
$$
114+
115+
The full expression has four sums corresponding to the four terms in the numerator $1+x+y+z$. The exact formulas are:
116+
117+
$$
118+
\begin{aligned}
119+
a(m,n,k)
120+
&= \sum_{N=\max(m,n,k)}^{\lfloor (m+n+k)/2 \rfloor}
121+
\binom{N}{m+n+k-2N, N-m, N-n, N-k} 2^{m+n+k-2N} \\
122+
&\quad + \sum_{N=\max(m-1,n,k)}^{\lfloor (m+n+k-1)/2 \rfloor}
123+
\binom{N}{m+n+k-2N-1, N-m+1, N-n, N-k} 2^{m+n+k-2N-1} \\
124+
&\quad + \sum_{N=\max(m,n-1,k)}^{\lfloor (m+n+k-1)/2 \rfloor}
125+
\binom{N}{m+n+k-2N-1, N-m, N-n+1, N-k} 2^{m+n+k-2N-1} \\
126+
&\quad + \sum_{N=\max(m,n,k-1)}^{\lfloor (m+n+k-1)/2 \rfloor}
127+
\binom{N}{m+n+k-2N-1, N-m, N-n, N-k+1} 2^{m+n+k-2N-1}
128+
\end{aligned}
129+
$$
130+
131+
There may be room to simplify this further; the symmetry in $m,n,k$ could help.
132+
133+
## Complexity
134+
135+
- **Recursion with memoization (DP):** $\Theta(m \cdot n \cdot k)$ time and space.
136+
- **Closed form:** Precompute factorials, then loop over $N$; time and space $\Theta(m+n+k)$, ignoring the cost of arithmetic on large integers.
137+
138+
## Implementation
139+
140+
Both the recursive and closed-form versions in Python:
141+
142+
```python
143+
import functools
144+
import math
145+
146+
@functools.lru_cache(maxsize=None)
147+
def a_rec(m: int, n: int, k: int) -> int:
148+
if min(m, n, k) < 0:
149+
return 0
150+
if m + n + k == 0 or m + n + k == 1:
151+
return 1
152+
if m + n == 0 or m + k == 0 or n + k == 0:
153+
return 0
154+
return (
155+
2 * a_rec(m - 1, n - 1, k - 1)
156+
+ a_rec(m - 1, n - 1, k)
157+
+ a_rec(m - 1, n, k - 1)
158+
+ a_rec(m, n - 1, k - 1)
159+
)
160+
161+
@functools.lru_cache(maxsize=None)
162+
def _binom4(N: int, a: int, b: int, c: int) -> int:
163+
r = N - a - b - c
164+
vals = sorted([a, b, c, r])
165+
assert vals[0] >= 0
166+
return math.factorial(N) // (
167+
math.factorial(vals[0]) * math.factorial(vals[1])
168+
* math.factorial(vals[2]) * math.factorial(vals[3])
169+
)
170+
171+
def a_closed(m: int, n: int, k: int) -> int:
172+
if min(m, n, k) < 0:
173+
return 0
174+
s = 0
175+
for N in range(max(m, n, k), (m + n + k) // 2 + 1):
176+
s += _binom4(N, N - m, N - n, N - k) * 2 ** (m + n + k - 2 * N)
177+
for N in range(max(m - 1, n, k), (m + n + k - 1) // 2 + 1):
178+
s += _binom4(N, N - m + 1, N - n, N - k) * 2 ** (m + n + k - 2 * N - 1)
179+
for N in range(max(m, n - 1, k), (m + n + k - 1) // 2 + 1):
180+
s += _binom4(N, N - m, N - n + 1, N - k) * 2 ** (m + n + k - 2 * N - 1)
181+
for N in range(max(m, n, k - 1), (m + n + k - 1) // 2 + 1):
182+
s += _binom4(N, N - m, N - n, N - k + 1) * 2 ** (m + n + k - 2 * N - 1)
183+
return s
184+
185+
# Sanity check
186+
r, r1 = a_rec(100, 200, 210), a_closed(100, 200, 210)
187+
print(f"Recursive: {r}, Closed: {r1}, Match: {r == r1}")
188+
```
189+
190+
We did not exploit the symmetry $a(m,n,k) = a(\sigma(m,n,k))$ for permutations $\sigma$; it could speed up computation but does not obviously simplify the closed expression.
191+
192+
---
193+
194+
*Originally answered on [Math Stack Exchange][math-se].*
195+
196+
[two-var-recursive-func]: /post/two-var-recursive-func/
197+
[math-se]: https://math.stackexchange.com/questions/1093271/how-to-solve-this-multivariable-recursion/2730331#2730331

0 commit comments

Comments
 (0)