Ecuación diofántica lineal
Una ecuación diofántica lineal (en dos variables) es una ecuación de la forma general:
donde , , son enteros dados, y , son enteros desconocidos.
En este artículo consideramos varios problemas clásicos sobre estas ecuaciones:
- encontrar una solución
- encontrar todas las soluciones
- encontrar la cantidad de soluciones y las soluciones mismas en un intervalo dado
- encontrar una solución con valor mínimo de
El caso degenerado
Un caso degenerado que hay que cuidar es cuando . Es fácil ver que o no hay soluciones o hay infinitas, según si o no. En el resto de este artículo ignoramos este caso.
Solución analítica
Cuando y , la ecuación se puede tratar de forma equivalente como cualquiera de las siguientes:
Sin pérdida de generalidad, supongamos que y consideremos la primera ecuación. Cuando y son coprimos, la solución se da como
donde es el inverso modular de módulo .
Cuando y no son coprimos, los valores de módulo para todo entero son divisibles por , así que la solución solo existe cuando es divisible por . En este caso, una de las soluciones se puede encontrar reduciendo la ecuación por :
Por definición de , los números y son coprimos, así que la solución se da explícitamente como
Solución algorítmica
El lema de Bézout (también llamado identidad de Bézout) es un resultado útil para entender la siguiente solución.
Sea . Entonces existen enteros tales que .
Además, es el menor entero positivo que se puede escribir como ; todos los enteros de la forma son múltiplos de .
Para encontrar una solución de la ecuación diofántica con 2 incógnitas, se puede usar el algoritmo de Euclides extendido. Primero, supongamos que y son no negativos. Cuando aplicamos el algoritmo de Euclides extendido a y , podemos encontrar su máximo común divisor y 2 números e tales que:
Si es divisible por , entonces la ecuación diofántica dada tiene solución; si no, no tiene ninguna. La demostración es directa: una combinación lineal de dos números es divisible por su divisor común.
Ahora supongamos que es divisible por , entonces tenemos:
Por lo tanto una de las soluciones de la ecuación diofántica es:
La idea de arriba sigue funcionando cuando o o ambos son negativos. Solo hay que cambiar el signo de e cuando sea necesario.
Por último, podemos implementar esta idea así (nótese que este código no considera el caso ):
int gcd(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int x1, y1;
int d = gcd(b, a % b, x1, y1);
x = y1;
y = x1 - y1 * (a / b);
return d;
}
bool find_any_solution(int a, int b, int c, int &x0, int &y0, int &g) {
g = gcd(abs(a), abs(b), x0, y0);
if (c % g) {
return false;
}
x0 *= c / g;
y0 *= c / g;
if (a < 0) x0 = -x0;
if (b < 0) y0 = -y0;
return true;
}Obtener todas las soluciones
A partir de una solución , podemos obtener todas las soluciones de la ecuación dada.
Sea y sean enteros que satisfacen lo siguiente:
Ahora, deberíamos ver que sumar a y, al mismo tiempo, restar de no rompe la igualdad:
Obviamente, este proceso se puede repetir, así que todos los números de la forma:
son soluciones de la ecuación diofántica dada.
Como la ecuación es lineal, todas las soluciones yacen sobre la misma recta, y por definición de este es el conjunto de todas las soluciones posibles de la ecuación diofántica dada.
Encontrar la cantidad de soluciones y las soluciones en un intervalo dado
De la sección anterior debería quedar claro que si no imponemos restricciones sobre las soluciones, habría infinitas. Así que en esta sección añadimos restricciones sobre el intervalo de e , y vamos a intentar contar y enumerar todas las soluciones.
Sean dos intervalos: y y digamos que solo queremos encontrar las soluciones en estos dos intervalos.
Nótese que si o es , entonces el problema solo tiene una solución. No consideramos ese caso acá.
Primero, podemos encontrar una solución que tenga el valor mínimo de tal que . Para esto, primero encontramos cualquier solución de la ecuación diofántica. Después, desplazamos esta solución para obtener (usando lo que sabemos del conjunto de todas las soluciones en la sección anterior). Esto se puede hacer en . Denotemos este valor mínimo de por .
De forma similar, podemos encontrar el valor máximo de que satisface . Denotemos este valor máximo de por .
De forma similar, podemos encontrar el valor mínimo de y el valor máximo de . Denotemos los valores correspondientes de por y .
La solución final son todas las soluciones con en la intersección de y . Denotemos esta intersección por .
A continuación está el código que implementa esta idea. Nótese que dividimos y al principio por . Como la ecuación es equivalente a , podemos usar esta última y tener , lo que simplifica las fórmulas.
void shift_solution(int & x, int & y, int a, int b, int cnt) {
x += cnt * b;
y -= cnt * a;
}
int find_all_solutions(int a, int b, int c, int minx, int maxx, int miny, int maxy) {
int x, y, g;
if (!find_any_solution(a, b, c, x, y, g))
return 0;
a /= g;
b /= g;
int sign_a = a > 0 ? +1 : -1;
int sign_b = b > 0 ? +1 : -1;
shift_solution(x, y, a, b, (minx - x) / b);
if (x < minx)
shift_solution(x, y, a, b, sign_b);
if (x > maxx)
return 0;
int lx1 = x;
shift_solution(x, y, a, b, (maxx - x) / b);
if (x > maxx)
shift_solution(x, y, a, b, -sign_b);
int rx1 = x;
shift_solution(x, y, a, b, -(miny - y) / a);
if (y < miny)
shift_solution(x, y, a, b, -sign_a);
if (y > maxy)
return 0;
int lx2 = x;
shift_solution(x, y, a, b, -(maxy - y) / a);
if (y > maxy)
shift_solution(x, y, a, b, sign_a);
int rx2 = x;
if (lx2 > rx2)
swap(lx2, rx2);
int lx = max(lx1, lx2);
int rx = min(rx1, rx2);
if (lx > rx)
return 0;
return (rx - lx) / abs(b) + 1;
}Una vez que tenemos y , también es simple enumerar todas las soluciones. Solo hay que iterar sobre para todo hasta , y encontrar los valores de correspondientes usando la ecuación .
Encontrar la solución con valor mínimo de { data-toc-label=‘Find the solution with minimum value of ’ }
Acá, e también necesitan alguna restricción; si no, la respuesta puede volverse menos infinito.
La idea es similar a la sección anterior: encontramos cualquier solución de la ecuación diofántica, y después desplazamos la solución para satisfacer algunas condiciones.
Por último, usamos el conocimiento del conjunto de todas las soluciones para encontrar el mínimo:
Nótese que cambia así:
Si , hay que elegir el menor valor posible de . Si , hay que elegir el mayor valor posible de . Si , todas las soluciones tendrán la misma suma .