Linear Diophantine Equations
Key Points
- β’A linear Diophantine equation ax + by = c has integer solutions if and only if gcd(a, b) divides c.
- β’The Extended Euclidean Algorithm finds one particular solution by producing Bezout coefficients for a and b.
- β’All solutions form an infinite arithmetic progression (a lattice line): + t and - t where ,b).
- β’Counting solutions in ranges reduces to finding the valid interval of the parameter t using careful floor/ceil arithmetic.
- β’Systems of linear Diophantine equations can be reduced to a single equation in a parameter or to congruences solved by CRT-like reasoning.
- β’Careful handling of signs, zero coefficients, and 64-bit overflow is essential in competitive programming implementations.
- β’Runtime is dominated by Extended GCD, which runs in O(log min(|a|, |b|)) time and constant space.
- β’Modular equations a x β‘ b (mod m) and CRT are direct corollaries of linear Diophantine theory and often appear in CF problems.
Prerequisites
- βGreatest Common Divisor and Euclidean Algorithm β Understanding gcd and how to compute it efficiently is necessary for solvability and for the Extended Euclidean Algorithm.
- βModular Arithmetic Basics β Interpreting congruences and residues connects Diophantine equations to modular equations and CRT.
- βInteger Division and Rounding β Correctly transforming interval constraints requires sign-safe floor and ceil operations.
- βOverflow and Integer Types β Products and scaling can exceed 64-bit; using wider intermediates like __int128 prevents errors.
- βLinear Algebra (2Γ2 Determinant) β Determinant indicates dependence of equations when solving systems and simplifies coefficients.
Detailed Explanation
Tap terms for definitions01Overview
Linear Diophantine equations are equations of the form ax + by = c, where a, b, c are given integers and we seek integer solutions (x, y). These appear everywhere: from solving modular equations to aligning periodic events and resource allocation problems. The key property is that solutions exist exactly when the greatest common divisor g = gcd(a, b) divides c. When solutions exist, the Extended Euclidean Algorithm (EEA) gives one specific solution (x0, y0). From there, all solutions can be described compactly using a single integer parameter t, producing a line of lattice points spaced regularly. This structure lets us efficiently answer more advanced questions: find any solution, find a solution with minimal |x|, count how many solutions lie inside a box [xL, xR] Γ [yL, yR], or solve systems of two equations by eliminating variables or translating them into congruences solved via the Chinese Remainder Theorem (CRT). In competitive programming (e.g., CF 1500β1900), mastering these transformations and implementing robust arithmetic (including safe floor/ceil for negative numbers and overflow-aware multiplications) is crucial. The algorithms run in logarithmic time with O(1) memory, so even very large inputs (up to 64-bit) are feasible with careful coding.
02Intuition & Analogies
Imagine a grid of streets forming integer coordinate points. The equation ax + by = c describes a tilted straight line on this grid. We are interested only in points where streets cross (integer coordinates). If the line passes through at least one intersection, it will pass through a whole infinite chain of them at fixed intervals; if it misses, it misses all of them. Why? Think of gcd(a, b) as the "gridβs step size" along the direction defined by (a, b). You can only move along combinations of a steps in x and b steps in y, and the net value ax + by moves in chunks of size gcd(a, b). Therefore, c must line up with that chunking or else the line never hits an intersection. Once we find a single intersection point (x0, y0), sliding along the line by adding b/g to x and subtracting a/g from y keeps you on the same line because ax + by remains c. This slide is like moving from one equally spaced lamp post to the next along a straight road. If you fence off a rectangular area [xL, xR] Γ [yL, yR] and want to count how many lamp posts are inside, you donβt need to scan the whole gridβyou just find the first t that lands in the box and the last t that still stays in, then count how many steps fit. For multiple lines (a system), you can parameterize one line and plug it into the other to reduce the problem to a single parameter equation, or translate one into a modular constraint and combine constraints using a CRT-like merge.
03Formal Definition
04When to Use
Use linear Diophantine methods when: (1) You must solve ax + by = c over integers, not realsβcommon in number theory and scheduling problems. (2) You need to solve modular equations ax β‘ b (mod m); convert to ax + m y = b and apply EEA. (3) You want to count or enumerate solutions within bounds; parameterize all solutions and intersect with range constraints. (4) You face two linear equations in two integer variables; parameterize one equation and insert into the other to find a common solution or detect inconsistency. (5) You need CRT merges of congruences, especially with non-coprime moduli; extended GCD and Diophantine reasoning unify the approach. (6) You are minimizing a linear cost (e.g., minimize |x|, minimize ax + by under constraints) over integer solutions; shift along the solution family to the best candidate. In CF problems (1500β1900), expect tasks like finding any solution, counting solutions in rectangles, merging congruences, or solving mixed constraints that translate to Diophantine equations. The tools here run in O(log n), so they are ideal for large 64-bit inputs when implemented carefully.
β οΈCommon Mistakes
- Ignoring the gcd condition: attempting to solve when gcd(a, b) β€ c leads to bogus arithmetic. Always check divisibility first. - Mishandling zero coefficients: if a = 0 or b = 0, the equation reduces to a single-variable constraint; handle these early to avoid division by zero. - Wrong parameter shifts: using x = x0 + bΒ·t (instead of b/g) or mixing signs in yβs update; always divide steps by g and remember y decreases by (a/g) when x increases by (b/g). - Incorrect floor/ceil with negative numbers: using naive integer division for ranges can shrink or expand intervals incorrectly. Implement robust floor_div and ceil_div that handle signs. - Overflow in 64-bit: products like a2b1 - b2a1 or scaling x0, y0 by c/g can overflow. Use 128-bit intermediates (__int128) and cast back after range checks. - Forgetting normalization in modular equations: solving ax β‘ b (mod m) requires reducing by g = gcd(a, m) then working modulo m/g. - CRT with non-coprime moduli: assuming a solution always exists; in reality, r1 β‘ r2 (mod gcd(m1, m2)) is required. - Not considering negative moduli or steps: ensure step sizes and divisions work regardless of sign; write sign-safe helpers. - Returning any solution when the problem asks for one in a specific range or minimal absolute value; remember to shift to meet constraints. - Skipping degeneracy checks in systems: if the determinant (a1 b2 β a2 b1) vanishes, equations may be dependent (infinite solutions) or inconsistent (no solution).
Key Formulas
GCD
Explanation: The greatest common divisor g is the largest integer dividing both a and b. It sets the granularity of values that ax + by can achieve.
Solvability Criterion
Explanation: The linear combination ax + by changes in steps of size g. Therefore c must be a multiple of g for integer solutions to exist.
Bezout Identity
Explanation: Extended GCD returns integers s, t such that this holds. Scaling by c/g yields a particular solution to ax + by = c.
General Solution
Explanation: All integer solutions lie on a line with step vector (b/g, βa/g). Starting from any one solution (x0, y0), varying t enumerates all.
t-interval for Box Constraints
Explanation: Converting x and y range constraints into bounds on t yields the intersection interval. The number of integer t in this interval is the count of solutions in the box.
System Reduction Coefficient
Explanation: After parameterizing solutions of a1x + b1y = c1, substitution into a2x + b2y = c2 yields K t = RHS. This K equals the determinant divided by g1.
Parameter from Second Equation
Explanation: A second equation pins down the parameter t uniquely if solvability requires K | RHS. Then x and y follow from the general solution.
Modular-to-Diophantine
Explanation: A modular equation is equivalent to a linear Diophantine equation in two variables. Existence requires gcd(a, m) | r.
CRT Consistency
Explanation: Two congruences can be merged if their residues agree modulo the gcd of their moduli. The combined modulus is lcm(m1, m2).
LCM-GCD Relation
Explanation: The least common multiple of two positive integers relates to their product and gcd. It is the modulus of the merged congruence in CRT.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct EGResult { 5 long long g; // gcd(a, b) 6 long long x; // coefficient for a 7 long long y; // coefficient for b 8 }; 9 10 // Extended Euclidean Algorithm: returns g = gcd(a, b) and x, y with ax + by = g 11 EGResult extended_gcd(long long a, long long b) { 12 if (b == 0) { 13 return { llabs(a), (a >= 0 ? 1 : -1), 0 }; 14 } 15 EGResult t = extended_gcd(b, a % b); 16 long long g = t.g; 17 long long x1 = t.y; 18 long long y1 = t.x - (a / b) * t.y; 19 return { g, x1, y1 }; 20 } 21 22 // Try to find any solution to ax + by = c. Returns false if none. 23 bool find_any_solution(long long a, long long b, long long c, 24 long long &x0, long long &y0, long long &g) { 25 if (a == 0 && b == 0) { 26 if (c == 0) { x0 = y0 = 0; g = 0; return true; } // all (x,y) are solutions 27 return false; // 0x + 0y = c has no solution if c != 0 28 } 29 EGResult eg = extended_gcd(a, b); 30 g = eg.g; 31 if (c % g != 0) return false; 32 // Scale the Bezout coefficients by c/g. Use 128-bit temporarily to avoid overflow. 33 __int128 scale = c / g; 34 __int128 xi = (__int128)eg.x * scale; 35 __int128 yi = (__int128)eg.y * scale; 36 x0 = (long long)xi; 37 y0 = (long long)yi; 38 return true; 39 } 40 41 // Shift a known solution by cnt steps along the solution line. 42 void shift_solution(long long &x, long long &y, long long a, long long b, long long g, long long cnt) { 43 // x += cnt * (b/g), y -= cnt * (a/g) 44 __int128 nx = (__int128)x + (__int128)cnt * (b / g); 45 __int128 ny = (__int128)y - (__int128)cnt * (a / g); 46 x = (long long)nx; 47 y = (long long)ny; 48 } 49 50 int main() { 51 ios::sync_with_stdio(false); 52 cin.tie(nullptr); 53 54 long long a, b, c; 55 // Example: solve 30x + 18y = 6 56 a = 30; b = 18; c = 6; 57 long long x0, y0, g; 58 if (!find_any_solution(a, b, c, x0, y0, g)) { 59 cout << "No solution\n"; 60 return 0; 61 } 62 cout << "One solution: x0=" << x0 << ", y0=" << y0 << " (gcd=" << g << ")\n"; 63 // Enumerate a few solutions by shifting t = -2..2 64 for (long long t = -2; t <= 2; ++t) { 65 long long x = x0, y = y0; 66 shift_solution(x, y, a, b, g, t); 67 cout << "t=" << t << ": x=" << x << ", y=" << y << "; check a*x+b*y=" << (a*x + b*y) << "\n"; 68 } 69 return 0; 70 } 71
Uses Extended GCD to compute a particular solution to ax + by = c and shows how to generate all solutions by stepping along the lattice line using the parameter t. The shift uses the canonical step vector (b/g, βa/g).
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct EGResult { long long g, x, y; }; 5 6 EGResult extended_gcd(long long a, long long b) { 7 if (b == 0) return { llabs(a), (a >= 0 ? 1 : -1), 0 }; 8 EGResult t = extended_gcd(b, a % b); 9 return EGResult{ t.g, t.y, t.x - (a / b) * t.y }; 10 } 11 12 bool find_any_solution(long long a, long long b, long long c, 13 long long &x0, long long &y0, long long &g) { 14 if (a == 0 && b == 0) { if (c == 0) { x0 = y0 = 0; g = 0; return true; } else return false; } 15 EGResult eg = extended_gcd(a, b); g = eg.g; 16 if (c % g != 0) return false; 17 __int128 scale = c / g; 18 x0 = (long long)((__int128)eg.x * scale); 19 y0 = (long long)((__int128)eg.y * scale); 20 return true; 21 } 22 23 // Floor division a // b towards -infinity, handles negatives correctly. 24 long long floor_div(long long a, long long b) { 25 assert(b != 0); 26 long long q = a / b; long long r = a % b; 27 if (r != 0 && ((r > 0) != (b > 0))) --q; // adjust when trunc towards 0 differs from floor 28 return q; 29 } 30 31 // Ceil division towards +infinity, handles negatives correctly. 32 long long ceil_div(long long a, long long b) { 33 assert(b != 0); 34 long long q = a / b; long long r = a % b; 35 if (r != 0 && ((r > 0) == (b > 0))) ++q; 36 return q; 37 } 38 39 // Count integer solutions (x, y) to ax + by = c with x in [xL,xR], y in [yL,yR] 40 long long count_in_box(long long a, long long b, long long c, 41 long long xL, long long xR, long long yL, long long yR) { 42 if (xL > xR || yL > yR) return 0; 43 long long x0, y0, g; 44 if (!find_any_solution(a, b, c, x0, y0, g)) return 0; 45 if (g == 0) { // a = b = c = 0: whole plane; count is (xR-xL+1)*(yR-yL+1) 46 __int128 cnt = (__int128)(xR - xL + 1) * (yR - yL + 1); 47 if (cnt > LLONG_MAX) return LLONG_MAX; // saturated for safety 48 return (long long)cnt; 49 } 50 long long stepX = b / g; // delta x per t 51 long long stepY = a / g; // delta y per t (note: y = y0 - stepY * t) 52 53 // Derive t interval from x bounds: x = x0 + stepX * t β [xL, xR] 54 long long tLx = ceil_div(xL - x0, stepX); 55 long long tRx = floor_div(xR - x0, stepX); 56 if (tLx > tRx) return 0; // no t satisfies x-bounds 57 58 // Derive t interval from y bounds: y = y0 - stepY * t β [yL, yR] 59 // Equivalent to y0 - yR β€ stepY * t β€ y0 - yL 60 long long tLy = ceil_div(y0 - yR, stepY); 61 long long tRy = floor_div(y0 - yL, stepY); 62 63 long long tL = max(tLx, tLy); 64 long long tR = min(tRx, tRy); 65 if (tL > tR) return 0; 66 // number of integers in [tL, tR] 67 __int128 cnt = (__int128)(tR - tL + 1); 68 if (cnt > LLONG_MAX) return LLONG_MAX; 69 return (long long)cnt; 70 } 71 72 int main() { 73 ios::sync_with_stdio(false); 74 cin.tie(nullptr); 75 76 // Example: Count solutions to 30x + 18y = 6 with 0 β€ x β€ 10, 0 β€ y β€ 10 77 long long a = 30, b = 18, c = 6; 78 long long xL = 0, xR = 10, yL = 0, yR = 10; 79 cout << count_in_box(a, b, c, xL, xR, yL, yR) << "\n"; 80 81 return 0; 82 } 83
Counts solutions by transforming the box constraints into an interval for the parameter t. It uses sign-safe floor/ceil division so that negative steps and values are handled correctly. Saturation is shown for the fully degenerate a = b = c = 0 case.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct EGResult { long long g, x, y; }; 5 6 EGResult extended_gcd(long long a, long long b) { 7 if (b == 0) return { llabs(a), (a >= 0 ? 1 : -1), 0 }; 8 EGResult t = extended_gcd(b, a % b); 9 return { t.g, t.y, t.x - (a / b) * t.y }; 10 } 11 12 bool find_any_solution(long long a, long long b, long long c, 13 long long &x0, long long &y0, long long &g) { 14 if (a == 0 && b == 0) { if (c == 0) { x0 = y0 = 0; g = 0; return true; } else return false; } 15 EGResult eg = extended_gcd(a, b); g = eg.g; 16 if (c % g != 0) return false; 17 __int128 scale = c / g; 18 x0 = (long long)((__int128)eg.x * scale); 19 y0 = (long long)((__int128)eg.y * scale); 20 return true; 21 } 22 23 // Solve the system: 24 // a1 x + b1 y = c1 25 // a2 x + b2 y = c2 26 // Returns (true, x, y) if a solution exists; handles degenerate cases. 27 bool solve_two_diophantine(long long a1, long long b1, long long c1, 28 long long a2, long long b2, long long c2, 29 long long &x, long long &y) { 30 // Step 1: Solve the first equation 31 long long x0, y0, g1; 32 if (!find_any_solution(a1, b1, c1, x0, y0, g1)) return false; 33 34 // General solution of the first: x = x0 + (b1/g1)t, y = y0 - (a1/g1)t 35 // Substitute into the second equation to solve for t: 36 // a2*(x0 + (b1/g1)t) + b2*(y0 - (a1/g1)t) = c2 37 // ((a2*b1 - b2*a1)/g1) * t + (a2*x0 + b2*y0) = c2 38 39 __int128 det = (__int128)a2 * b1 - (__int128)b2 * a1; // a2*b1 - b2*a1 40 long long K; // K = det / g1 (must be divisible) 41 if (det % g1 != 0) return false; // no way to get integer t 42 K = (long long)(det / g1); 43 44 __int128 rhs = (__int128)c2 - ((__int128)a2 * x0 + (__int128)b2 * y0); 45 46 if (K == 0) { 47 // Second equation becomes (a2*x0 + b2*y0) = c2; rhs must be 0 48 if (rhs != 0) return false; // inconsistent parallel lines 49 // Infinitely many solutions along t; return one (t = 0) 50 x = x0; y = y0; return true; 51 } 52 53 if (rhs % K != 0) return false; // t must be integer 54 long long t = (long long)(rhs / K); 55 56 // Compute final x, y 57 __int128 xi = (__int128)x0 + (__int128)(b1 / g1) * t; 58 __int128 yi = (__int128)y0 - (__int128)(a1 / g1) * t; 59 x = (long long)xi; y = (long long)yi; 60 return true; 61 } 62 63 int main() { 64 ios::sync_with_stdio(false); 65 cin.tie(nullptr); 66 67 // Example system: 68 // 6x + 9y = 3 69 // 10x + 7y = 1 70 long long a1=6,b1=9,c1=3; long long a2=10,b2=7,c2=1; 71 long long x,y; 72 if (solve_two_diophantine(a1,b1,c1,a2,b2,c2,x,y)) { 73 cout << "x=" << x << ", y=" << y << "\n"; 74 cout << "Check1: " << a1*x + b1*y << " == " << c1 << "\n"; 75 cout << "Check2: " << a2*x + b2*y << " == " << c2 << "\n"; 76 } else { 77 cout << "No solution\n"; 78 } 79 return 0; 80 } 81
Parameterizes all solutions of the first equation, substitutes into the second, and solves for the parameter t. This mirrors a CRT-like reduction where the second equation pins down the residue class of the parameter. Degenerate cases with zero determinant are handled explicitly.
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct EGResult { long long g, x, y; }; 5 6 EGResult extended_gcd(long long a, long long b) { 7 if (b == 0) return { llabs(a), (a >= 0 ? 1 : -1), 0 }; 8 EGResult t = extended_gcd(b, a % b); 9 return EGResult{ t.g, t.y, t.x - (a / b) * t.y }; 10 } 11 12 // Solve a x β‘ b (mod m). Returns (ok, x0, mod), where all solutions are x β‘ x0 (mod mod). 13 tuple<bool, long long, long long> solve_linear_mod(long long a, long long b, long long m) { 14 EGResult eg = extended_gcd(a, m); 15 long long g = eg.g; 16 if (b % g != 0) return {false, 0, 0}; 17 __int128 scale = b / g; 18 // Minimal non-negative solution 19 __int128 x0 = ((__int128)eg.x * scale) % (m / g); 20 if (x0 < 0) x0 += (m / g); 21 return {true, (long long)x0, m / g}; 22 } 23 24 // Merge x β‘ r1 (mod m1) and x β‘ r2 (mod m2). Returns (ok, r, lcm) 25 // Works with non-coprime moduli. 26 tuple<bool, long long, long long> crt_merge(long long r1, long long m1, long long r2, long long m2) { 27 // Normalize residues 28 r1 %= m1; if (r1 < 0) r1 += m1; 29 r2 %= m2; if (r2 < 0) r2 += m2; 30 31 EGResult eg = extended_gcd(m1, m2); 32 long long g = eg.g; 33 if ((r2 - r1) % g != 0) return {false, 0, 0}; // inconsistency 34 35 // Solve m1 * k β‘ (r2 - r1) (mod m2) 36 __int128 k = (__int128)(r2 - r1) / g; 37 __int128 mod2_g = m2 / g; 38 __int128 inv = eg.x; // inverse of m1/g modulo m2/g 39 __int128 t = (k * inv) % mod2_g; 40 if (t < 0) t += mod2_g; 41 42 __int128 lcm = (__int128)m1 / g * m2; 43 __int128 r = (__int128)r1 + t * m1; 44 r %= lcm; if (r < 0) r += lcm; 45 return {true, (long long)r, (long long)lcm}; 46 } 47 48 int main() { 49 ios::sync_with_stdio(false); 50 cin.tie(nullptr); 51 52 // Example 1: Solve 12 x β‘ 18 (mod 30) 53 { 54 auto [ok, x0, mod] = solve_linear_mod(12, 18, 30); 55 if (ok) cout << "x β‘ " << x0 << " (mod " << mod << ")\n"; 56 else cout << "No solution\n"; 57 } 58 59 // Example 2: Merge x β‘ 2 (mod 6) and x β‘ 3 (mod 15) 60 { 61 auto [ok, r, m] = crt_merge(2, 6, 3, 15); 62 if (ok) cout << "x β‘ " << r << " (mod " << m << ")\n"; 63 else cout << "Inconsistent congruences\n"; 64 } 65 66 return 0; 67 } 68
Shows two core corollaries: solving a modular linear equation via Diophantine methods, and merging two congruences using a generalized CRT that works for non-coprime moduli. This is the CRT-like technique often needed when reducing systems.