Algoritmo de Euclides extendido
Algoritmo de Euclides
| Fuente | Recurso | Notas |
|---|---|---|
| cp-algo | Euclidean |
El algoritmo de Euclides original calcula y se ve así:
ll euclid(ll a, ll b) {
while (b > 0) {
ll k = a / b;
a -= k * b;
swap(a, b);
}
return a;
}static long euclid(long a, long b) {
while (b > 0) {
long k = a / b;
a -= k * b;
long temp = b;
b = a;
a = temp;
}
return a;
}def euclid(a: int, b: int) -> int:
assert (
int(a) > 0 and int(b) > 0
), "Arguments must be positive, non-zero numeric values."
while b > 0:
k = a // b
# subtract multiples of one equation from the other.
a -= b * k
a, b = b, a
return aAlgoritmo de Euclides extendido
| Fuente | Recurso | Notas |
|---|---|---|
| cp-algo | Extended Euclidean | |
| Wikipedia | Extended Euclidean | |
| cp-algo | Linear Diophantine Equation |
El algoritmo de Euclides extendido calcula enteros e tales que
¡Podemos modificar ligeramente la versión del algoritmo de Euclides dada arriba para devolver más información!
array<ll, 3> extend_euclid(ll a, ll b) {
// we know that (1 * a) + (0 * b) = a and (0 * a) + (1 * b) = b
array<ll, 3> x = {1, 0, a};
array<ll, 3> y = {0, 1, b};
// run extended Euclidean algo
while (y[2] > 0) {
// keep subtracting multiple of one equation from the other
ll k = x[2] / y[2];
for (int i = 0; i < 3; i++) { x[i] -= k * y[i]; }
swap(x, y);
}
return x; // x[0] * a + x[1] * b = x[2], x[2] = gcd(a, b)
}static long[] extendEuclid(long a, long b) {
// we know that 1 * a + 0 * b = a and 0 * a + 1 * b = b
long[] x = {1, 0, a};
long[] y = {0, 1, b};
// run extended Euclidean algo
while (y[2] > 0) {
// keep subtracting multiple of one equation from the other
long k = x[2] / y[2];
for (int i = 0; i < 3; i++) { x[i] -= k * y[i]; }
long[] temp = x;
x = y;
y = temp;
}
return x; // x[0] * a + x[1] * b = x[2], x[2] = gcd(a, b)
}def extend_euclid(a: int, b: int) -> list[int]:
assert (
int(a) > 0 and int(b) > 0
), "Arguments must be positive, non-zero numeric values."
# we know that 1 * a + 0 * b = a and 0 * a + 1 * b = b.
x_arr = [1, 0, int(a)]
y_arr = [0, 1, int(b)]
q = -1
while y_arr[2] > 0: # run extended Euclidean algo.
q = x_arr[2] // y_arr[2]
for i in range(3):
# keep subtracting multiple of one equation from the other.
x_arr[i] -= y_arr[i] * q
x_arr, y_arr = y_arr, x_arr
return x_arr # (x[0] * a) + (x[1] * b) = x[2], x[2] = gcd(a, b)Versión recursiva
ll euclid(ll a, ll b) {
if (b == 0) { return a; }
return euclid(b, a % b);
}static long euclid(long a, long b) {
if (b == 0) { return a; }
return euclid(b, a % b);
}def euclid(a: int, b: int) -> int:
"""Recursive Euclidean GCD."""
return a if b == 0 else euclid(b, a % b)se convierte en
pl extend_euclid(ll a, ll b) { // returns {x,y}
if (b == 0) { return {1, 0}; }
pl p = extend_euclid(b, a % b);
return {p.s, p.f - a / b * p.s};
}static long[] extendEuclid(long a, long b) { // returns {x,y}
if (b == 0) { return new long[] {1, 0}; }
long[] p = extendEuclid(b, a % b);
return new long[] {p[1], p[0] - a / b * p[1]};
}def extend_euclid(a: int, b: int) -> list[int]:
if not b:
return [1, 0]
p = extend_euclid(b, a % b)
return [p[1], p[0] - (a // b) * p[1]]El par será igual a los primeros dos elementos devueltos del arreglo en la versión iterativa. Mirando esta versión, podemos demostrar por inducción que cuando y son enteros positivos distintos, el par devuelto cumplirá y . ¡Además, solo puede existir un par que cumpla estas condiciones!
Observar que esto funciona cuando son bastante grandes (digamos, ) y no tendremos problemas de desbordamiento.
Aplicación - Inverso modular
| Fuente | Recurso | Notas |
|---|---|---|
| cp-algo | Modular Inverse |
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| Kattis | Modular Arithmetic | N/A | en el módulo |
Parece que cuando hay multiplicación / división involucrada en este problema, n^2 < \texttt{LLONG\\_MAX}.
ll inv_general(ll a, ll b) {
array<ll, 3> x = extend_euclid(a, b);
assert(x[2] == 1); // gcd must be 1
return x[0] + (x[0] < 0) * b;
}static long invGeneral(long a, long b) {
long[] x = extendEuclid(a, b);
assert (x[2] == 1); // gcd must be 1
return x[0] + (x[0] < 0 ? 1 : 0) * b;
}def inv_general(a: int, b: int) -> int:
"""Returns the modular inverse of two positive, non-zero integer values."""
arr = extend_euclid(a, b)
assert arr[2] == 1, "GCD must be 1."
return arr[0] + (arr[0] < 0) * bAplicación - Teorema Chino del Resto
| Fuente | Recurso | Notas |
|---|---|---|
| Stanford | The Chinese Remainder Theorem | Explicación en profundidad |
| cp-algo | Chinese Remainder Theorem | |
| CMU | Chinese Remainder Theorem |
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| Kattis | Chinese Remainder | N/A | en el módulo |
Para dos congruencias
Explicación
El Teorema Chino del Resto — también llamado CRT — produce una solución única a un sistema de congruencias modulares simultáneas con módulos coprimos dos a dos. Más específicamente, el CRT determina un número que al ser dividido por los divisores dados deja los restos dados. Matemáticamente, resuelve el siguiente sistema de congruencias modulares:
El sistema de arriba tiene exactamente una solución módulo .
Nos enfocaremos en el caso particular ; para dos módulos coprimos:
- Sean y el inverso modular de módulo y el inverso modular de módulo , respectivamente, de modo que se cumplen las propiedades: y . Vale mencionar que y siempre existen porque . Podemos definir una solución como:
Satisface ambas ecuaciones:
- Módulo tenemos ya que
- Módulo tenemos ya que
Ahora que tenemos una solución solo queda mostrar que es en efecto la única solución válida módulo . Asumiendo que hay una solución válida distinta tenemos: , , , , , entonces se sigue que divide a , ya que son relativamente primos. Esto significa que: .
Implementación
Complejidad temporal:
#include <algorithm>
#include <array>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
typedef long long ll;
// Extended Euclidean Algorithm
array<ll, 3> extend_euclid(ll a, ll b) {
// we know that (1 * a) + (0 * b) = a and (0 * a) + (1 * b) = b
array<ll, 3> x = {1, 0, a};
array<ll, 3> y = {0, 1, b};
while (y[2] > 0) {
// keep subtracting the multiple of one equation from the other
ll k = x[2] / y[2];
for (int i = 0; i < 3; i++) { x[i] -= k * y[i]; }
swap(x, y);
}
return x; // x[0] * a + x[1] * b = x[2], x[2] = gcd(a, b)
}
// The Modular Inverse
ll inv(ll x, ll y) {
ll m = extend_euclid(x, y)[0] % y;
return (m < 0 ? m + y : m);
}
// General code for multiple congruences in a system
ll crt(const vector<ll> &remainders, const vector<ll> &moduli) {
ll MOD = accumulate(moduli.begin(), moduli.end(), 1LL, multiplies<ll>());
ll x = 0;
for (int i = 0; i < (int)moduli.size(); i++) {
ll a = remainders[i] * inv(MOD / moduli[i], moduli[i]) % moduli[i];
x = (x + a * (MOD / moduli[i])) % MOD;
}
return x;
}
int main() {
int tests;
cin >> tests;
for (int t = 0; t < tests; t++) {
vector<ll> r(2), m(2);
cin >> r[0] >> m[0] >> r[1] >> m[1];
cout << crt(r, m) << ' ' << m[0] * m[1] << '\n';
}
}Para varias congruencias
Usando las mismas notaciones que en , consideremos — el producto de todos los módulos excluyendo — y . Por argumentos similares a los de antes, la solución única módulo es: