I’ll summarize my approach so that it is more convenient for people to improve it, if someone is interested.
We are given positive integers a, b, c, d such that \gcd(a, b) = 1 and \gcd(c, d) = 1. We are trying to find smallest positive n>0 such that
n = i a + j b = k c + ld,
where i, j, k, l \in \mathbb{N} are non-negative variables.
My approach is based on finding natural solutions of the linear equations. Let’s first focus on the first one, n = ia + jb.
From the Bézout’s identity, we can find i_0, j_0 \in \mathbb{Z} such that
ai_0 + b j_0 = 1.
Multiplying by some n \in \mathbb{N}, we get
a (ni_0) + b(n j_0) = n.
Since either i_0 or j_0 is negative (unless a = b = 1), we don’t have a natural solution. Suppose i_0 is negative. We can write
a(n i_0 + b m) + b(n j_0 - am) = n,
where m \in \mathbb{N} is some number. The goal now is to find the smallest n \in \mathbb{N} such that there is some m \in \mathbb{N} that makes both ni_0 + bm and n j_0 - am non-negative. Formally:
n i_0 + bm \geq 0 \rightarrow m \geq -\frac{ni_0}{b}, \\
n j_0 - am \geq 0 \rightarrow m \leq \frac{nj_0}{a},
which gives together
n s \leq m \leq n t,
where s = -\frac{i_0}{b} and t = \frac{j_0}{a}. Now, if j_0 was negative, we would get s = -\frac{j_0}{a} and t = \frac{i_0}{b}.
So, our task transforms into finding the smallest n \in \mathbb{N} for which there is an m \in \mathbb{N} such that n s \leq m \leq n t. Subtract ns to get
0 \leq m - ns \leq n ( t - s ),
in which we set m = \lceil ns \rceil to satisfy the first inequality. It leads to finding the smallest n \in \mathbb{N} such that
\begin{aligned}
\lceil ns \rceil - ns &\leq n ( t - s) \\
\lceil ns \rceil &\leq n t
\end{aligned}
If we make the very same argument but for the equation n = kc + ld, we obtain
\lceil nu \rceil \leq nv,
where u = \begin{cases}
-k_0 / d & \text{if } k_0 < 0 \\
-l_0 / c & \text{if } l_0 < 0
\end{cases} and v = \begin{cases}
l_0 / c & \text{if } k_0 < 0 \\
k_0 / d & \text{if } l_0 < 0
\end{cases} with the Bézout’s coefficients k_0, l_0 such that c k_0 + d l_0 = 1.
So the algorithm is:
- Initialize n := 1
- Find smallest n_1 \geq n for which \lceil n_1 s \rceil \leq n_1 t holds
- Find smallest n_2 \geq n for which \lceil n_2 u \rceil \leq n_2 v holds
- Set n := \max\{ n_1, n_2 \}
- If n_1 \neq n_2, go to 2.
- Compute solution.
The solution can be computed as
\begin{aligned}
i &= i_0 n - \text{sign}(i_0) b m_1, \\
j &= j_0 n + \text{sign}(i_0) a m_1, \\
k &= k_0 n - \text{sign}(k_0) d m_2, \\
l &= l_0 n + \text{sign}(k_0) c m_2,
\end{aligned}
where m_1 = \lceil ns \rceil and m_2 = \lceil nu \rceil.
The problem with this approach is that for some inputs, we have to iterate over too many n before finding a match n_1 = n_2. We could improve that by initializing n with a value that is closer to the optimum. Another improvement could be in steps 2 and 3, where now I sequentially check if the condition holds (maybe we could do skip some values).
Code here:
Code
function next_lowest(u, v, n)
while n <= typemax(Int)
if n * v - ceil(n * u) ≥ 0
return n
end
# Improvement: do larger steps
n += 1
end
throw(ErrorException("not found"))
end
function solve(a, b, c, d)
_, i0, j0 = gcdx(a, b)
_, k0, l0 = gcdx(c, d)
s = ifelse(i0 < 0, -i0 // b, -j0 // a)
t = ifelse(i0 < 0, j0 // a, i0 // b)
u = ifelse(k0 < 0, -k0 // d, -l0 // c)
v = ifelse(k0 < 0, l0 // c, k0 // d)
# Improvement: Initialize better
n = 1
while true
n1 = next_lowest(s, t, n)
n2 = next_lowest(u, v, n)
n = max(n1, n2)
if n1 == n2
break
end
end
println("Solved for n=$n")
m1 = ceil(n * s)
m2 = ceil(n * u)
i = Int(i0 * n - sign(i0) * b * m1)
j = Int(j0 * n + sign(i0) * a * m1)
k = Int(k0 * n - sign(k0) * d * m2)
l = Int(l0 * n + sign(k0) * c * m2)
println("i=", i)
println("j=", j)
println("k=", k)
println("l=", l)
end