Fracciones continuas
Una fracción continua (continued fraction) es una representación de un número real como una secuencia convergente específica de números racionales. Son útiles en programación competitiva porque son fáciles de computar y se pueden usar de forma eficiente para encontrar la mejor aproximación racional posible del número real subyacente (entre todos los números cuyo denominador no exceda un valor dado).
Además de eso, las fracciones continuas están estrechamente relacionadas con el algoritmo de Euclides, lo que las hace útiles en un montón de problemas de teoría de números.
Representación en fracción continua
Definición
se llama la representación en fracción continua del número racional y se denota de forma breve como .
Ejemplo
Se puede demostrar que cualquier número racional se puede representar como fracción continua de exactamente formas:
Además, la longitud de tal fracción continua se estima como para .
El razonamiento detrás de esto quedará claro una vez que profundicemos en los detalles de la construcción de la fracción continua.
Definición
se llama la representación en fracción continua del número irracional y se denota de forma breve como .
Nótese que para y un entero , se cumple que .
Otra observación importante es que cuando y cuando .
Definición
Correspondientemente, un individual se llama el -ésimo convergente de .
Ejemplo
donde es la razón áurea y . Así,
Nótese que en este caso específico, una forma alternativa de encontrar sería resolver la ecuación
Definición
Típicamente nos referiremos a (semi)convergentes que son mayores que como (semi)convergentes superiores y a los que son menores que como (semi)convergentes inferiores.
Definición
Correspondientemente, llamaremos a un individual el -ésimo cociente completo de .
De las definiciones de arriba, se puede concluir que para .
Tratando como una expresión algebraica formal y permitiendo números reales arbitrarios en lugar de , obtenemos
En particular, . Por otro lado, podemos expresar como
lo que significa que podemos computar y a partir de .
La secuencia está bien definida salvo que , lo cual solo ocurre cuando es un número racional.
Así, la representación en fracción continua está definida de forma única para cualquier número irracional .
Implementación
En los fragmentos de código asumiremos principalmente fracciones continuas finitas.
A partir de , la transición a se ve como
De esta expresión, el siguiente cociente completo se obtiene como
Para significa que
Así, el cómputo de una representación en fracción continua para sigue los pasos del algoritmo de Euclides para y .
De esto también se sigue que para . Por tanto, los convergentes son siempre irreducibles.
=== “C++”
cpp auto fraction(int p, int q) { vector<int> a; while(q) { a.push_back(p / q); tie(p, q) = make_pair(q, p % q); } return a; }
=== “Python”
py def fraction(p, q): a = [] while q: a.append(p // q) p, q = q, p % q return a
Resultados clave
Para dar alguna motivación para el estudio posterior de las fracciones continuas, damos ahora algunos hechos clave.
Recurrencia
donde y .
Desviaciones
Multiplicando ambos lados por , obtenemos una estimación alternativa:
De la recurrencia de arriba se sigue que crece al menos tan rápido como los números de Fibonacci.
En la imagen de abajo se puede ver la visualización de cómo los convergentes se aproximan a :
está representado por la línea punteada azul. Los convergentes impares se aproximan desde arriba y los convergentes pares se aproximan desde abajo.
Envolventes de retículo
Los convergentes impares son los vértices de la envolvente superior, mientras que los convergentes pares son los vértices de la envolvente inferior.
Todos los vértices enteros de las envolventes se obtienen como tales que
para entero . En otras palabras, el conjunto de puntos de retículo en las envolventes corresponde al conjunto de semiconvergentes.
En la imagen de abajo, se pueden ver los convergentes y semiconvergentes (puntos grises intermedios) de .
Mejores aproximaciones
Entonces es un semiconvergente de .
El último hecho permite encontrar las mejores aproximaciones racionales de comprobando sus semiconvergentes.
Abajo se encontrará la explicación adicional y un poco de intuición e interpretación de estos hechos.
Convergentes
Miremos más de cerca los convergentes que se definieron antes. Para , sus convergentes son
Los convergentes son el concepto central de las fracciones continuas, así que es importante estudiar sus propiedades.
Para el número , su -ésimo convergente se puede computar como
donde es el continuante , un polinomio multivariado definido como
_{\textstyle .}
Así, es una medianta ponderada de y .
Por consistencia, se definen dos convergentes adicionales y .
Explicación detallada
El numerador y el denominador de se pueden ver como polinomios multivariados de :
De la definición de convergentes,
De esto se sigue . Esto da la relación
Inicialmente, y , así
Por consistencia, es conveniente definir y y decir formalmente que y .
Del análisis numérico, se sabe que el determinante de una matriz tridiagonal arbitraria
se puede computar de forma recursiva como . Comparándolo con , obtenemos una expresión directa
_{\textstyle .}
Este polinomio también se conoce como el continuante por su estrecha relación con las fracciones continuas. El continuante no cambiará si se invierte la secuencia de la diagonal principal. Esto da una fórmula alternativa para computarlo:
Implementación
Computaremos los convergentes como un par de secuencias y :
=== “C++”
cpp auto convergents(vector<int> a) { vector<int> p = {0, 1}; vector<int> q = {1, 0}; for(auto it: a) { p.push_back(p[p.size() - 1] * it + p[p.size() - 2]); q.push_back(q[q.size() - 1] * it + q[q.size() - 2]); } return make_pair(p, q); }
=== “Python”
py def convergents(a): p = [0, 1] q = [1, 0] for it in a: p.append(p[-1]*it + p[-2]) q.append(q[-1]*it + q[-2]) return p, q
Árboles de fracciones continuas
Hay dos formas principales de unir todas las fracciones continuas posibles en estructuras de árbol útiles.
Árbol de Stern-Brocot
El árbol de Stern-Brocot es un árbol binario de búsqueda que contiene todos los números racionales positivos distintos.
El árbol se ve en general así:
Las fracciones y se mantienen “virtualmente” en los lados izquierdo y derecho del árbol respectivamente.
Luego la fracción en un nodo es una medianta de dos fracciones y por encima de él.
La recurrencia significa que la representación en fracción continua codifica el camino a en el árbol. Para encontrar , hay que hacer movimientos a la derecha, movimientos a la izquierda, movimientos a la derecha y así sucesivamente hasta .
El padre de entonces es la fracción obtenida al dar un paso atrás en la última dirección usada.
En otras palabras, es cuando y cuando .
Así los hijos de son y .
Indexemos el árbol de Stern-Brocot. Al vértice raíz se le asigna índice . Luego para un vértice , el índice de su hijo izquierdo se asigna cambiando el bit líder de de a y para el hijo derecho, se asigna cambiando el bit líder de a :
En esta indexación, la representación en fracción continua de un número racional especifica la codificación por longitud de racha (run-length encoding) de su índice binario.
Para , su índice es y su codificación por longitud de racha, considerando bits en orden ascendente, es .
Otro ejemplo es , que tiene índice y su codificación por longitud de racha es, en efecto, .
Vale la pena notar que el árbol de Stern-Brocot es, de hecho, un treap. Es decir, es un árbol binario de búsqueda por , pero es un heap tanto por como por .
Comparar fracciones continuas
Solución
Como ya mencionamos, en esta representación denota el número de giros a la derecha en el descenso, denota el número de giros a la izquierda consecuentes y así sucesivamente. Por lo tanto, cuando comparamos y , si deberíamos simplemente pasar a comparar y . En caso contrario, si estamos en descensos a la derecha, deberíamos comprobar si y si estamos en descensos a la izquierda, deberíamos comprobar si para decir si .
En otras palabras, para y irracionales sería si y solo si con comparación lexicográfica.
Ahora, usando formalmente como elemento de la representación en fracción continua es posible emular números irracionales y , es decir, elementos que son menores (mayores) que , pero mayores (menores) que cualquier otro número real. Específicamente, para , uno de estos dos elementos se puede emular como y el otro se puede emular como .
Cuál corresponde a y cuál a se puede determinar por la paridad de o comparándolos como números irracionales.
=== “Python” ```py # check if a < b assuming that a[-1] = b[-1] = infty and a != b def less(a, b): a = [(-1)**ia[i] for i in range(len(a))] b = [(-1)**ib[i] for i in range(len(b))] return a < b
# [a0; a1, ..., ak] -> [a0, a1, ..., ak-1, 1]
def expand(a):
if a: # empty a = inf
a[-1] -= 1
a.append(1)
return a
# return a-eps, a+eps
def pm_eps(a):
b = expand(a.copy())
a.append(float('inf'))
b.append(float('inf'))
return (a, b) if less(a, b) else (b, a)
```Mejor punto interior
Solución
Así, si y son números irracionales, el LCA es .
Para y racionales, uno de ellos podría ser el LCA mismo, lo que nos requeriría hacer análisis por casos. Para simplificar la solución para y racionales, es posible usar la representación en fracción continua de y que se derivó en el problema anterior.
=== “Python”
py # finds lexicographically smallest (q, p) # such that p0/q0 < p/q < p1/q1 def middle(p0, q0, p1, q1): a0 = pm_eps(fraction(p0, q0))[1] a1 = pm_eps(fraction(p1, q1))[0] a = [] for i in range(min(len(a0), len(a1))): a.append(min(a0[i], a1[i])) if a0[i] != a1[i]: break a[-1] += 1 p, q = convergents(a) return p[-1], q[-1]
[GCJ 2019, Round 2 - New Elements: Part 2](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/0000000000146184)
Entre tales pares, encontrar el lexicográficamente mínimo.
Solución
Entre tales ecuaciones tenemos cuatro grupos significativos para :
- se puede ignorar ya que buscamos .
- daría “IMPOSSIBLE” como respuesta.
- , . Tales restricciones son equivalentes a .
- , . Tales restricciones son equivalentes a .
Sea el mayor del cuarto grupo y el menor del tercer grupo.
El problema es ahora, dado , encontrar una fracción tal que es lexicográficamente el más pequeño y . === “Python” ```py def solve(): n = int(input()) C = [0] * n J = [0] * n # p0/q0 < y/x < p1/q1 p0, q0 = 0, 1 p1, q1 = 1, 0 fail = False for i in range(n): C[i], J[i] = map(int, input().split()) if i > 0: A = C[i] - C[i-1] B = J[i] - J[i-1] if A <= 0 and B <= 0: fail = True elif B > 0 and A < 0: # y/x > (-A)/B if B > 0 if (-A)q0 > p0B: p0, q0 = -A, B elif B < 0 and A > 0: # y/x < A/(-B) if B < 0 if Aq1 < p1(-B): p1, q1 = A, -B if p0q1 >= p1q0 or fail: return ‘IMPOSSIBLE’
p, q = middle(p0, q0, p1, q1)
return str(q) + ' ' + str(p)
```Árbol de Calkin-Wilf
Una forma algo más simple de organizar fracciones continuas en un árbol binario es el árbol de Calkin-Wilf .
El árbol se ve en general así:
En la raíz del árbol se ubica el número . Luego, para el vértice con un número , sus hijos son y .
A diferencia del árbol de Stern-Brocot, el árbol de Calkin-Wilf no es un árbol binario de búsqueda, así que no se puede usar para realizar búsqueda binaria racional.
En el árbol de Calkin-Wilf, el padre directo de una fracción es cuando y en caso contrario.
Para el árbol de Stern-Brocot, usamos la recurrencia de convergentes. Para trazar la conexión entre la fracción continua y el árbol de Calkin-Wilf, deberíamos recordar la recurrencia de los cocientes completos. Si , entonces .
Por otro lado, si vamos repetidamente de a su padre en el árbol de Calkin-Wilf cuando , terminaremos en . Si continuamos haciéndolo, terminaremos en , luego y así sucesivamente. De esto podemos deducir que:
- Cuando , el padre directo de en el árbol de Calkin-Wilf es .
- Cuando y , su padre directo es .
- Y cuando y , su padre directo es .
Correspondientemente, los hijos de son
- , que es ,
- , que es para y para .
Notablemente, si enumeramos los vértices del árbol de Calkin-Wilf en el orden de búsqueda en anchura (es decir, la raíz tiene número , y los hijos del vértice tienen índices y respectivamente), el índice del número racional en el árbol de Calkin-Wilf sería el mismo que en el árbol de Stern-Brocot.
Así, los números en los mismos niveles del árbol de Stern-Brocot y del árbol de Calkin-Wilf son los mismos, pero su ordenamiento difiere a través de la permutación de inversión de bits .
Convergencia
Para el número y su -ésimo convergente se cumple la siguiente fórmula:
En particular, significa que
y
De esto podemos concluir que
La última desigualdad se debe al hecho de que y generalmente se ubican en lados distintos de , así
Explicación detallada
Para estimar , empezamos estimando la diferencia entre convergentes adyacentes. Por definición,
Reemplazando y en el numerador con sus recurrencias, obtenemos
así el numerador de es siempre el numerador negado de . Este, a su vez, es igual a para
así
Esto da una representación alternativa de como suma parcial de una serie infinita:
De la relación recurrente se sigue que aumenta de forma monótona al menos tan rápido como los números de Fibonacci, así
siempre está bien definido, ya que la serie subyacente siempre converge. Notablemente, la serie residual
tiene el mismo signo que debido a lo rápido que disminuye . Por tanto los de índice par se aproximan a desde abajo mientras que los de índice impar se aproximan desde arriba:
De esta imagen podemos ver que
así la distancia entre y nunca es mayor que la distancia entre y :
¿Euclides extendido?
Solución
Sea . Se demostró arriba que . Sustituyendo y por y , obtenemos
donde . Si es divisible por , entonces la solución es e .
=== “Python”
py # return (x, y) such that Ax+By=C # assumes that such (x, y) exists def dio(A, B, C): p, q = convergents(fraction(A, B)) C //= A // p[-1] # divide by gcd(A, B) t = (-1) if len(p) % 2 else 1 return t*C*q[-2], -t*C*p[-2]
Transformaciones lineales fraccionarias
Otro concepto importante para las fracciones continuas son las llamadas transformaciones lineales fraccionarias (linear fractional transformations).
Definición
Una composición de transformaciones lineales fraccionarias y es ella misma una transformación lineal fraccionaria:
La inversa de una transformación lineal fraccionaria también es una transformación lineal fraccionaria:
[DMOPC '19 Contest 7 P4 - Bob and Continued Fractions](https://dmoj.ca/problem/dmopc19c7p4)
Solución
Es generalmente cierto que .
Denotemos . Nótese que . En esta noción, se cumple que
Así, el problema se reduce al cómputo de
La composición de transformaciones es asociativa, así que es posible computar en cada nodo de un Árbol de Segmentos la composición de transformaciones en su subárbol.
Transformación lineal fraccionaria de una fracción continua
Esto permite computar y para cualquier .
Solución
De ahí, agregando consecutivamente , y así sucesivamente seríamos capaces de computar
Como es invertible, también es monótona en . Por lo tanto, para cualquier se cumple que está entre y .
Además, para es igual a . De ahí, está entre y . Cuando son iguales, también son iguales a .
Nótese que . Conociendo , podemos componer con la transformación actual y continuar agregando , y así sucesivamente, buscando que los nuevos suelos coincidan, de lo cual podríamos deducir y así sucesivamente hasta recuperar todos los valores de .
Aritmética de fracciones continuas
Solución
En lugar de se cambiaría la transformación actual como o .
Luego, se comprueba si y si todos coinciden, se usa este valor como en la fracción resultante y se cambia la transformación como
Definición
Se dice que una fracción continua es eventualmente periódica si , donde es periódica.
Para se cumple que , así . Hay una conexión genérica entre las fracciones continuas periódicas y las ecuaciones cuadráticas. Consideremos la siguiente ecuación:
Por un lado, esta ecuación significa que la representación en fracción continua de es periódica con período .
Por otro lado, usando la fórmula de convergentes, esta ecuación significa que
Es decir, es una transformación lineal fraccionaria de sí misma. Se sigue de la ecuación que es una raíz de la ecuación de segundo grado:
Un razonamiento similar se aplica a las fracciones continuas que son eventualmente periódicas, es decir para . En efecto, de la primera ecuación derivamos que y de la segunda ecuación que , donde y son transformaciones lineales fraccionarias. Por lo tanto,
Se puede demostrar además (y lo hizo primero Lagrange) que para una ecuación cuadrática arbitraria con coeficientes enteros, su solución es una fracción continua eventualmente periódica.
Irracionalidad cuadrática
Solución
Por lo tanto,
Multiplicando el numerador y el denominador por , nos desharemos de en el denominador, así los cocientes completos son de la forma
Encontremos , asumiendo que es conocido.
Primero, . Luego,
Así, si denotamos , se cumplirá que
Lo bueno de tal representación es que si reducimos por su máximo común divisor, el resultado sería único. Por lo tanto, podemos usarlo para comprobar si el estado actual ya se ha repetido y también para comprobar cuál fue el índice anterior que tenía este estado.
Abajo está el código para computar la representación en fracción continua de :
=== “Python” ```py # compute the continued fraction of sqrt(n) def sqrt(n): n0 = math.floor(math.sqrt(n)) x, y, z = 1, 0, 1 a = [] def step(x, y, z): a.append((x * n0 + y) // z) t = y - a[-1]z x, y, z = -zx, zt, t**2 - nx**2 g = math.gcd(x, math.gcd(y, z)) return x // g, y // g, z // g
used = dict()
for i in range(n):
used[x, y, z] = i
x, y, z = step(x, y, z)
if (x, y, z) in used:
return a
```Usando la misma función step pero distintos , y iniciales es posible computarlo para un arbitrario.
[Tavrida NU Akai Contest - Continued Fraction](https://timus.online/problem.aspx?space=1&num=1814)
Solución
=== “Python” ```py x, k = map(int, input().split())
mod = 10**9+7
# compose (A[0]*x + A[1]) / (A[2]*x + A[3]) and (B[0]*x + B[1]) / (B[2]*x + B[3])
def combine(A, B):
return [t % mod for t in [A[0]*B[0]+A[1]*B[2], A[0]*B[1]+A[1]*B[3], A[2]*B[0]+A[3]*B[2], A[2]*B[1]+A[3]*B[3]]]
A = [1, 0, 0, 1] # (x + 0) / (0*x + 1) = x
a = sqrt(x)
T = len(a) - 1 # period of a
# apply ak + 1/x = (ak*x+1)/(1x+0) to (Ax + B) / (Cx + D)
for i in reversed(range(1, len(a))):
A = combine([a[i], 1, 1, 0], A)
def bpow(A, n):
return [1, 0, 0, 1] if not n else combine(A, bpow(A, n-1)) if n % 2 else bpow(combine(A, A), n // 2)
C = (0, 1, 0, 0) # = 1 / 0
while k % T:
i = k % T
C = combine([a[i], 1, 1, 0], C)
k -= 1
C = combine(bpow(A, k // T), C)
C = combine([a[0], 1, 1, 0], C)
print(str(C[1]) + '/' + str(C[3]))
```Interpretación geométrica
Sea para el convergente . Entonces, se cumple la siguiente recurrencia:
Sea . Entonces, cada vector corresponde al número que es igual a su coeficiente de pendiente .
Con la noción de producto seudoescalar , se puede mostrar (véase la explicación de abajo) que
La última ecuación se debe al hecho de que y yacen en lados distintos de , así los productos seudoescalares de y con tienen signos distintos. Con en mente, la fórmula para ahora se ve como
Nótese que , así
Explicación
En forma vectorial, se reescribe como
lo que significa que y son colineales (es decir, tienen el mismo coeficiente de pendiente). Tomando el producto seudoescalar de ambas partes con , obtenemos
lo que da la fórmula final
Algoritmo de estiramiento de nariz
Así, es el máximo número entero de vectores que se pueden agregar a sin cambiar el signo del producto cruz con .
En otras palabras, es el máximo número entero de veces que se puede agregar a sin cruzar la recta definida por :
En la imagen de arriba, se obtiene agregando repetidamente a .
Cuando no es posible agregar más a sin cruzar la recta , vamos al otro lado y agregamos repetidamente a para obtener .
Este procedimiento genera vectores exponencialmente más largos, que se aproximan a la recta.
Por esta propiedad, el procedimiento de generar vectores convergentes consecuentes fue llamado el algoritmo de estiramiento de nariz (nose stretching algorithm) por Boris Delaunay.
Si miramos el triángulo dibujado sobre los puntos , y notaremos que su área duplicada es
Combinado con el teorema de Pick, significa que no hay puntos de retículo estrictamente dentro del triángulo y los únicos puntos de retículo en su borde son y para todo entero tal que . Cuando se une para todos los posibles significa que no hay puntos enteros en el espacio entre los polígonos formados por los vectores convergentes de índice par e impar.
Esto, a su vez, significa que con coeficientes impares forman una envolvente convexa de puntos de retículo con por encima de la recta , mientras que con coeficientes pares forman una envolvente convexa de puntos de retículo con por debajo de la recta .
Definición
Estos polígonos también se conocen como polígonos de Klein, nombrados por Felix Klein quien primero sugirió esta interpretación geométrica de las fracciones continuas.
Ejemplos de problemas
Ahora que se introdujeron los hechos y conceptos más importantes, es hora de profundizar en ejemplos de problemas específicos.
Envolvente convexa bajo la recta
Solución
Sin embargo, con la restricción adicional necesitaríamos eventualmente desviarnos de la recta para mantener una envolvente convexa adecuada.
Sea , entonces los primeros puntos de retículo en la envolvente después de son para entero .
Sin embargo no puede ser el siguiente punto de retículo ya que es mayor que .
Para llegar a los siguientes puntos de retículo en la envolvente, deberíamos llegar al punto que diverge de por el menor margen, manteniendo .
Sea el último punto actual en la envolvente convexa. Entonces el siguiente punto es tal que y está tan cerca de la recta como sea posible. En otras palabras, maximiza sujeto a y .
Puntos como ese yacen en la envolvente convexa de puntos de retículo por debajo de . En otras palabras, debe ser un semiconvergente inferior de .
Dicho esto, es de la forma para algún número impar y .
Para encontrar tal , podemos recorrer todos los posibles empezando por el más grande y usar para tales que .
Con , la condición se preservaría por las propiedades de los semiconvergentes.
Y se cumpliría porque ya agotamos los semiconvergentes obtenidos de , de ahí es mayor que .
Ahora que podemos agregar a por veces antes de exceder , después de lo cual intentaríamos el siguiente semiconvergente.
=== “C++” ```cpp // returns [ah, ph, qh] such that points r[i]=(ph[i], qh[i]) constitute upper convex hull // of lattice points on 0 <= x <= N and 0 <= y <= r * x, where r = [a0; a1, a2, …] // and there are ah[i]-1 integer points on the segment between r[i] and r[i+1] auto hull(auto a, int N) { auto [p, q] = convergents(a); int t = N / q.back(); vector ah = {t}; vector ph = {0, tp.back()}; vector qh = {0, tq.back()};
for(int i = q.size() - 1; i >= 0; i--) {
if(i % 2) {
while(qh.back() + q[i - 1] <= N) {
t = (N - qh.back() - q[i - 1]) / q[i];
int dp = p[i - 1] + t * p[i];
int dq = q[i - 1] + t * q[i];
int k = (N - qh.back()) / dq;
ah.push_back(k);
ph.push_back(ph.back() + k * dp);
qh.push_back(qh.back() + k * dq);
}
}
}
return make_tuple(ah, ph, qh);
}
```=== “Python”
py # returns [ah, ph, qh] such that points r[i]=(ph[i], qh[i]) constitute upper convex hull # of lattice points on 0 <= x <= N and 0 <= y <= r * x, where r = [a0; a1, a2, ...] # and there are ah[i]-1 integer points on the segment between r[i] and r[i+1] def hull(a, N): p, q = convergents(a) t = N // q[-1] ah = [t] ph = [0, t*p[-1]] qh = [0, t*q[-1]] for i in reversed(range(len(q))): if i % 2 == 1: while qh[-1] + q[i-1] <= N: t = (N - qh[-1] - q[i-1]) // q[i] dp = p[i-1] + t*p[i] dq = q[i-1] + t*q[i] k = (N - qh[-1]) // dq ah.append(k) ph.append(ph[-1] + k * dp) qh.append(qh[-1] + k * dq) return ah, ph, qh
[Timus - Crime and Punishment](https://timus.online/problem.aspx?space=1&num=1430)
Solución
Por conveniencia, invertiremos la dirección de haciendo una sustitución , de modo que ahora hay que encontrar el punto tal que , y es el máximo posible. El óptimo para cada tiene valor .
Para tratarlo de forma más genérica, escribiremos una función que encuentra el mejor punto en e .
La idea central de la solución en este problema esencialmente repite el problema anterior, pero en lugar de usar semiconvergentes inferiores para divergir de la recta, se usan semiconvergentes superiores para acercarse a la recta sin cruzarla y sin violar . Desafortunadamente, a diferencia del problema anterior, hay que asegurarse de no cruzar la recta al acercarse a ella, así que se debe tener en cuenta al calcular el coeficiente del semiconvergente.
=== “Python” ```py # (x, y) such that y = (Ax+B) // C, # Cy - Ax is max and 0 <= x <= N. def closest(A, B, C, N): # y <= (Ax + B)/C <=> diff(x, y) <= B def diff(x, y): return Cy-Ax a = fraction(A, C) p, q = convergents(a) ph = [B // C] qh = [0] for i in range(2, len(q) - 1): if i % 2 == 0: while diff(qh[-1] + q[i+1], ph[-1] + p[i+1]) <= B: t = 1 + (diff(qh[-1] + q[i-1], ph[-1] + p[i-1]) - B - 1) // abs(diff(q[i], p[i])) dp = p[i-1] + tp[i] dq = q[i-1] + tq[i] k = (N - qh[-1]) // dq if k == 0: return qh[-1], ph[-1] if diff(dq, dp) != 0: k = min(k, (B - diff(qh[-1], ph[-1])) // diff(dq, dp)) qh.append(qh[-1] + kdq) ph.append(ph[-1] + kdp) return qh[-1], ph[-1]
def solve(A, B, N):
x, y = closest(A, N % A, B, N // A)
return N // A - x, y
```[June Challenge 2017 - Euler Sum](https://www.codechef.com/problems/ES)
Solución
Después de construir la envolvente convexa de los puntos por debajo de , este número se puede computar usando el teorema de Pick:
=== “C++” ```cpp // sum floor(k * x) for k in [1, N] and x = [a0; a1, a2, …] int sum_floor(auto a, int N) { N++; auto [ah, ph, qh] = hull(a, N);
// The number of lattice points within a vertical right trapezoid
// on points (0; 0) - (0; y1) - (dx; y2) - (dx; 0) that has
// a+1 integer points on the segment (0; y1) - (dx; y2).
auto picks = [](int y1, int y2, int dx, int a) {
int b = y1 + y2 + a + dx;
int A = (y1 + y2) * dx;
return (A - b + 2) / 2 + b - (y2 + 1);
};
int ans = 0;
for(size_t i = 1; i < qh.size(); i++) {
ans += picks(ph[i - 1], ph[i], qh[i] - qh[i - 1], ah[i - 1]);
}
return ans - N;
}
```=== “Python” ```py # sum floor(k * x) for k in [1, N] and x = [a0; a1, a2, …] def sum_floor(a, N): N += 1 ah, ph, qh = hull(a, N)
# The number of lattice points within a vertical right trapezoid
# on points (0; 0) - (0; y1) - (dx; y2) - (dx; 0) that has
# a+1 integer points on the segment (0; y1) - (dx; y2).
def picks(y1, y2, dx, a):
b = y1 + y2 + a + dx
A = (y1 + y2) * dx
return (A - b + 2) // 2 + b - (y2 + 1)
ans = 0
for i in range(1, len(qh)):
ans += picks(ph[i-1], ph[i], qh[i]-qh[i-1], ah[i-1])
return ans - N
```[NAIPC 2019 - It's a Mod, Mod, Mod, Mod World](https://open.kattis.com/problems/itsamodmodmodmodworld)
Solución
Sin embargo, sumar para de a es algo de lo que somos capaces del problema anterior.
=== “C++”
cpp void solve(int p, int q, int N) { cout << p * N * (N + 1) / 2 - q * sum_floor(fraction(p, q), N) << "\n"; }
=== “Python”
py def solve(p, q, N): return p * N * (N + 1) // 2 - q * sum_floor(fraction(p, q), N)
[Library Checker - Sum of Floor of Linear](https://judge.yosupo.jp/problem/sum_of_floor_of_linear)
Solución
Es posible usar el mismo enfoque y construir la envolvente convexa completa de puntos por debajo de la recta .
Ya sabemos cómo resolverlo para . Además, ya sabemos cómo construir esta envolvente convexa hasta el punto de retículo más cercano a esta recta en el segmento (esto se hace en el problema “Crime and Punishment” de arriba).
Ahora deberíamos notar que una vez que alcanzamos el punto más cercano a la recta, podemos simplemente asumir que la recta de hecho pasa por el punto más cercano, ya que no hay otros puntos de retículo en entre la recta real y la recta movida ligeramente abajo para pasar por el punto más cercano.
Dicho esto, para construir la envolvente convexa completa por debajo de la recta en , podemos construirla hasta el punto más cercano a la recta en y luego continuar como si la recta pasara por este punto, reutilizando el algoritmo para construir la envolvente convexa con :
=== “Python” ```py # hull of lattice (x, y) such that Cy <= Ax+B def hull(A, B, C, N): def diff(x, y): return Cy-Ax a = fraction(A, C) p, q = convergents(a) ah = [] ph = [B // C] qh = [0]
def insert(dq, dp):
k = (N - qh[-1]) // dq
if diff(dq, dp) > 0:
k = min(k, (B - diff(qh[-1], ph[-1])) // diff(dq, dp))
ah.append(k)
qh.append(qh[-1] + k*dq)
ph.append(ph[-1] + k*dp)
for i in range(1, len(q) - 1):
if i % 2 == 0:
while diff(qh[-1] + q[i+1], ph[-1] + p[i+1]) <= B:
t = (B - diff(qh[-1] + q[i+1], ph[-1] + p[i+1])) // abs(diff(q[i], p[i]))
dp = p[i+1] - t*p[i]
dq = q[i+1] - t*q[i]
if dq < 0 or qh[-1] + dq > N:
break
insert(dq, dp)
insert(q[-1], p[-1])
for i in reversed(range(len(q))):
if i % 2 == 1:
while qh[-1] + q[i-1] <= N:
t = (N - qh[-1] - q[i-1]) // q[i]
dp = p[i-1] + t*p[i]
dq = q[i-1] + t*q[i]
insert(dq, dp)
return ah, ph, qh
```[OKC 2 - From Modular to Rational](https://codeforces.com/gym/102354/problem/I)
Formulación equivalente: Encontrar que entrega el mínimo de para .
Solución
Podría haber varias soluciones posibles de para un resto dado . Sin embargo, si y son ambas soluciones entonces también se cumple que . Asumiendo que significa que es al menos .
En el enunciado se nos dijo que , así que si tanto como son a lo sumo , entonces la diferencia es a lo sumo . Para significa que la solución con es única, como número racional.
Así, el problema se reduce, dado módulo , a encontrar cualquier tal que y .
Esto es efectivamente lo mismo que encontrar que entrega el mínimo posible para .
Para significa que hay que encontrar un par tal que y es el mínimo posible.
Como es constante, podemos dividir por él y reenunciar además: encontrar tal que y es el mínimo posible.
En términos de fracciones continuas significa que es la mejor aproximación diofántica a y basta con comprobar solo los semiconvergentes inferiores de .
=== “Python”
py # find Q that minimizes Q*r mod m for 1 <= k <= n < m def mod_min(r, n, m): a = fraction(r, m) p, q = convergents(a) for i in range(2, len(q)): if i % 2 == 1 and (i + 1 == len(q) or q[i+1] > n): t = (n - q[i-1]) // q[i] return q[i-1] + t*q[i]
Problemas de práctica
- UVa OJ - Continued Fractions
- ProjectEuler+ #64: Odd period square roots
- Codeforces Round #184 (Div. 2) - Continued Fractions
- Codeforces Round #201 (Div. 1) - Doodle Jump
- Codeforces Round #325 (Div. 1) - Alice, Bob, Oranges and Apples
- POJ Founder Monthly Contest 2008.03.16 - A Modular Arithmetic Challenge
- 2019 Multi-University Training Contest 5 - fraction
- SnackDown 2019 Elimination Round - Election Bait
- Code Jam 2019 round 2 - Continued Fraction