Exploré algunos sitios de desafíos de ciencias de la computación/teoría de los números por diversión, y presentaron el siguiente problema, exactamente como sigue:
Dejemos que $$P(n) = \sum_{k=1}^n \varphi(k)$$
Encuentre $P(10^{16})$
He buscado durante bastante tiempo sobre esto y he probado diferentes enfoques:
-
Utilizando la fórmula de $$\varphi(n)= n \cdot \prod_{i=1}^k \frac{p_i-1}{p_i}$$ Intenté calcular cada $\varphi(n)$ en el rango, pero esto se vuelve muy ineficiente para grandes $n$ . Podría llegar hasta $10^7$ con este enfoque. Más allá de esto, se vuelve demasiado lento.
-
Intenté una diferente, más directa. Wikipedia y Wolfram Alpha sugieren fórmulas similares para calcular directamente $P(n)$ : $$P(n) = \sum_{k=1}^n \varphi(k)= \frac12 \cdot \biggl (1+ \sum_{k=1}^n\mu (k)\cdot \lfloor {\frac nk} \rfloor^2\biggl)$$ Esta fórmula parecía mucho más prometedora. La probé y conseguí llegar mucho más lejos que $10^7$ pero aún está lejos del objetivo. Calculando previamente un tamiz de la función Moebius, pude llegar a un poco menos de $10^9$ . Mi memoria era insuficiente y no podía calcular más valores en un colador. E incluso si pudiera, todavía toma mucho tiempo y está muy lejos de $10^{16}$ .
Aquí está parte del código que he utilizado para mi segundo enfoque escrito en Java:
public static BigInteger PhiSummatoryFunction (long limit)
{
BigInteger sum = BigInteger.ZERO;
int [] m = MoebiusSieve(limit);
for (int i=1;i<m.length;i++)
sum=sum.add(BigInteger.valueOf((long) (m[i]*Math.floor(limit/i)*Math.floor(limit/i))));
return sum.add(BigInteger.ONE).divide(BigInteger.ONE.add(BigInteger.ONE));
}
Donde MoebiusSieve es una función que calcula los valores de la función Moebius hasta un determinado límite en un tamiz, utilizando un método similar al de Eratóstenes.
- Después de entender e implementar el método recursivo sugerido en un enlace proporcionado en los comentarios: $$P(n) = \frac {n(n+1)}{2} - \sum_{i=2}^\sqrt n P(\lfloor \frac ni \rfloor) - \sum_{j=1}^\sqrt n P(j) \cdot (\lfloor \frac nj \rfloor - \lfloor \frac n{j+1} \rfloor)$$
Puedo calcular valores hasta $P(10^{11})$ y con la máxima asignación de memoria, precalculando tantos $\varphi(n)$ como sea posible y, en consecuencia, todos los $P(n)$ que puedo para la memoización, puedo calcular $P(10^{12})$ en poco más de 20 minutos. Una gran mejora, pero todavía un poco lejos de $P(10^{16})$ . No pasa nada si el cálculo tarda un poco más, pero me temo que $P(10^{16})$ tardaría un tiempo exponencialmente mayor, a juzgar por el "salto" en el tiempo de cálculo entre $P(10^{11})$ y $P(10^{12})$ . Mi memoria me permite "guardar" hasta $350,000,000 \space (n)$ valores O hasta $700,000,000 \space (k)$ valores. ¿Quizás haya una forma de realizar la suma utilizando los valores (k) en lugar de los (n)?
Todos mis cálculos sugieren y demuestran que mi recursión es la que más tiempo consume. Esto es obvio, pero estoy seguro de que consume más tiempo del que debería, como señala qwr . Así que a continuación pongo el código de recursión, con algo de documentación. Me parece que esta es la forma correcta de hacer este cálculo, pero mi implementación no es óptima.
public static BigInteger phiR (long limit, long [] s) // limit is 10^t, s is the sieve of precomputed values of `P(n)`. Can store maximum 350,000,000 values
{
if (limit<s.length)
return BigInteger.valueOf(s[(int) limit]);
BigInteger sum = BigInteger.valueOf(limit).multiply(BigInteger.valueOf(limit).add(BigInteger.ONE)).divide(BigInteger.valueOf(2)); // this corresponds to the n'th triangular number
BigInteger midsum1=BigInteger.ZERO;
BigInteger midsum2=BigInteger.ZERO;
for (long m=2;m*m<=limit;m++) // computing the first sum, first for changing floor(limit/m) values
midsum1=midsum1.add(phiR((long) Math.floor(limit/m),s));
for (long d=1;d*d<=limit;d++) // computing the second sum
if ((double)d!=Math.floor(limit/d))
midsum2=midsum2.add(BigInteger.valueOf((long) (Math.floor(limit/d)-Math.floor(limit/(d+1)))).multiply(phiR(d,s)));
sum=sum.subtract(midsum1).subtract(midsum2);
return sum;
}
Me sugirieron que usara dictámenes por qwr además de la matriz, para los valores grandes de $n$ pero no sé nada al respecto. Se puede hacer otra mejora para que el plazo sea de un día más o menos?
2 votos
Por favor, no utilice $\phi$ por esto. Muchos autores lo utilizan para la propia función totiente.
0 votos
Bueno, moebius es $0$ en muchos casos. ¿Qué pasa si se extraen los primos que deben tomar la raíz cuadrada y generar los casos que el $\mu$ no es $0$
3 votos
En realidad: mathproblems123.wordpress.com/2018/05/10/
0 votos
@Phicar Creo que puede mejorar los cálculos para rangos pequeños, pero aun así no me serviría. Seguiría necesitando el $\mu$ para los números más grandes. Además, me temo que generar primos hasta ese rango es imposible
0 votos
@darijgrinberg Siguiendo las ideas proporcionadas en el enlace, puedo conseguir valores hasta $P(10^{12})$ , tal y como se actualiza en el post. ¿Se puede hacer alguna mejora adicional para llegar a $P(10^{16})$ i
0 votos
@qwr El bucle 'while' sólo determina cuántas veces los valores de los pisos de $n/j$ cambiar constantemente entre $j$ 's. A continuación, para los valores que son constantes en rangos variables, comprueba cuántas veces aparece cada uno y añade el valor de $P(n)$ en consecuencia, multiplicado por ese factor. Debería ahorrar algo de tiempo, pero incluso sustituyendo esta parte por un simple bucle "for" no mejora el tiempo en absoluto.
0 votos
No sé a qué te refieres con "cuántas veces cambian los valores de los pisos". Lee atentamente mi respuesta enlazada y asegúrate de que sabes lo que hacen las funciones floor. No te estoy pidiendo que sustituyas los bucles while por bucles for; te estoy preguntando por qué tienes bucles while.
0 votos
@qwr He reeditado el código para eliminar el bucle while, ahora aparece en la pregunta. Sigue tardando lo mismo. Incluso utilizando toda la memoria posible que puedo precalculando todos los $P(n)$ valores hasta $350,000,000$ , $P(10^{12})$ tarda 20 minutos :/
0 votos
Sin duda, obtendrá un aumento de velocidad si memoriza todos los valores de $P(n)$ . Una vez más lo he dicho en mi respuesta vinculada.
1 votos
No entiendo " Mi memoria me permite "guardar" hasta $350,000,000 \varphi(n)$ valores O hasta $700,000,000 \mu(k)$ valores ". $\mu(k)$ sólo puede tomar tres valores diferentes, por lo que requiere menos de 2 bits de espacio de almacenamiento.
1 votos
¡Ayer mismo conseguí calcularlo, @qwr ! Tu respuesta fue clave para hacerlo. Gracias.
0 votos
Este sitio está estructurado en torno a preguntas y respuestas. Se hace más difícil de leer cuando se rompe ese modelo mental básico. Así que si más tarde encuentras la respuesta a tu propia pregunta, lo mejor es publicarla como respuesta, no editar la pregunta. Te sugiero que elimines la actualización, la pongas a continuación en una respuesta, tal vez expliques la derivación, y hagas clic en la marca de verificación para seleccionarla como respuesta aceptada. Como ventaja para ti, esto permitiría a la gente votar tanto la pregunta como la respuesta, dándote 15 rep en lugar de 5.
0 votos
Es posible que obtengas mejor ayuda para optimizar el código preguntando en stackoverflow o en codereview stackexchange. Dicho esto, no sé por qué tienes ningún bucle while. Si miras la fórmula que tú y yo damos, sólo hay dos sumas que se calculan con bucles for. Implementa la fórmula directamente.