Programa para encontrar todos los números primos en un rango dado muy grande de números enteros

Me encontré con la siguiente pregunta en un sitio web de programación: Peter quiere generar algunos números primos para su criptosistema. ¡Ayúdalo! ¡Tu tarea es generar todos los números primos entre dos números dados!

Entrada

La entrada comienza con el número t de casos de prueba en una sola línea (t <= 10). En cada una de las siguientes t líneas hay dos números myn (1 <= m <= n <= 1000000000, nm <= 100000) separados por un espacio.

Se me ocurrió la siguiente solución:

import java.util.*;

public class PRIME1 {
    static int numCases;
    static int left, right;
    static boolean[] initSieve = new boolean[32000];
    static boolean[] answer;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        numCases = sc.nextInt();
        initSieve[0] = true;
        initSieve[1] = true;
        Sieve();
        for (int j = 0; j < numCases; j++) {
            String line = sc.next();
            String line2 = sc.next();
            left = Integer.parseInt(line);
            right = Integer.parseInt(line2);
            answer = new boolean[right - left + 1];
            getAnswer();
            for (int i = 0; i < answer.length; i++) {
                if (!answer[i]) {
                    int ans = i + left;
                    System.out.println(ans);
                }
            }
            System.out.println();
        }
    }

    public static void Sieve() {

        for (int i = 2; i < 32000; i++) {
            if (!initSieve[i]) {
                for (int j = 2 * i; j < 32000; j += i) {
                    initSieve[j] = true;
                }
            }
            if (i * i > 32000)
                break;
        }
    }

    public static void getAnswer() {
        for (int i = 2; i < 32000 && i <= right; i++) {
            if (!initSieve[i]) {
                int num = i;
                if (num * 2 >= left) {
                    num *= 2;
                } else {
                    num = (num * (left / num));
                    if (num < left)
                        num += i;
                }
                for (int j = num; j >= left && j <= right; j += i) {
                    answer[j - left] = true;
                }
            }
        }
    }
}

He editado mi solución después de leer algunas de las sugerencias. Sigo recibiendo un tipo de error de límite de tiempo excedido. ¿Alguna sugerencia más sobre cómo optimizar aún más esto? Estoy calculando todos los números primos hasta 32000 y luego utilizándolos para encontrar los números primos entre n y m.

Gracias, Rohit

preguntado el 22 de mayo de 12 a las 15:05

Si comienza en 3, no en 2 en el tamiz, y logra cambiar el valor por 2 fuera del ciclo, puede iterar por i+=2;. Luego, en lugar de ejecutar isNotPrime.length en el ciclo externo, √(isNotPrime.length) debería ser suficiente. Sin relación: el escáner tiene un método nextInt. -

Puede reducir a la mitad su tiempo de ejecución al instante recorriendo solo las probabilidades en el rango, comenzando desde un número impar e incrementando por i+=2 in for (int i = 0; i < answer.length; i+=2). Asegúrate de eso i corresponde a un número impar no debajo de su left. Ningún número par por encima de 2 será primo. :) Ese sería un esquema de direccionamiento escaso, incluso más rápido es trabajar con una matriz comprimida, donde la entrada en el índice i representa el número n=left_odd + 2i. Dentro Sieve(), trabajo mediante j+=2*i también (aunque este tamiz es muy pequeño) pero más importante, dentro getAnswer(). Mira la respuesta de Daniel. -

6 Respuestas

Se le da

1 <= m <= n <= 1000000000, nm<=100000

estos son números muy pequeños. Para tamizar un rango con un límite superior de n, necesitas los primos para √n. aquí ya sabes n <= 10^9, asi que √n < 31623, por lo que, en el peor de los casos, necesita los números primos para 31621. Hay 3401. Puede generarlos con un tamiz estándar en unos pocos microsegundos.

Entonces simplemente puede tamizar el pequeño rango de m a n marcando los múltiplos de los primos que ha tamizado antes, deteniéndose cuando el primo excede √n. Se puede ganar algo de aceleración eliminando los múltiplos de algunos números primos pequeños del tamiz, pero la lógica se vuelve más complicada (es necesario tratar los tamices con números primos pequeños). m especialmente).

public int[] chunk(int m, int n) {
    if (n < 2) return null;
    if (m < 2) m = 2;
    if (n < m) throw new IllegalArgumentException("Borked");
    int root = (int)Math.sqrt((double)n);
    boolean[] sieve = new boolean[n-m+1];
    // primes is the global array of primes to 31621 populated earlier
    // primeCount is the number of primes stored in primes, i.e. 3401
    // We ignore even numbers, but keep them in the sieve to avoid index arithmetic.
    // It would be very simple to omit them, though.
    for(int i = 1, p = primes[1]; i < primeCount; ++i) {
        if ((p = primes[i]) > root) break;
        int mult;
        if (p*p < m) {
            mult = (m-1)/p+1;
            if (mult % 2 == 0) ++mult;
            mult = p*mult;
        } else {
            mult = p*p;
        }
        for(; mult <= n; mult += 2*p) {
            sieve[mult-m] = true;
        }
    }
    int count = m == 2 ? 1 : 0;
    for(int i = 1 - m%2; i < n-m; i += 2) {
        if (!sieve[i]) ++count;
    }
    int sievedPrimes[] = new int[count];
    int pi = 0;
    if (m == 2) {
        sievedPrimes[0] = 2;
        pi = 1;
    }
    for(int i = 1 - m%2; i < n-m; i += 2) {
        if (!sieve[i]) {
            sievedPrimes[pi++] = m+i;
        }
    }
    return sievedPrimes;
}

El uso de un BitSet o cualquier otro tipo de matriz de banderas empaquetadas reduciría el uso de la memoria y, por lo tanto, podría proporcionar una aceleración significativa debido a una mejor ubicación de caché.

contestado el 22 de mayo de 12 a las 16:05

He editado mi solución de acuerdo con su sugerencia, pero todavía tengo una situación de límite de tiempo excedido. ¿Podría sugerir cómo optimizar aún más? - Rohit Agrawal

¿Cuál es el límite de tiempo y cuál es la tarea? Si debe imprimir todos los primos, ese es probablemente el cuello de botella. No estoy bien versado en E/S de Java, por lo que no podría ofrecer consejos sobre cómo acelerar la impresión. Si solo debe imprimir el número (o la suma) de números primos en cada intervalo, eso no debería ser un problema. Esta implementación realiza la configuración y tamiza 100 intervalos de aproximadamente 100000 de longitud en menos de 90 ms aquí, el tiempo de inicio de la JVM es mayor. Por supuesto, las máquinas de prueba pueden ser más lentas y tener cachés más pequeñas, por lo que reemplazar el boolean[] con BitSetS puede ser bueno. - Daniel Fischer

Use un BitSet en lugar de un Array of Boolean.

public static BitSet primes (final int MAX)
{
     BitSet primes = new BitSet (MAX);
     // make only odd numbers candidates...
     for (int i = 3; i < MAX; i+=2)
     {
        primes.set(i);
     }
     // ... except no. 2
     primes.set (2, true);
     for (int i = 3; i < MAX; i+=2)
     {
        /*
            If a number z is already  eliminated (like 9),
             because it is itself a multiple of a prime 
            (example: 3), then all multiples of z (9) are
            already eliminated.
        */
        if (primes.get (i))
        {
            int j = 3 * i;
            while (j < MAX)
            {
                if (primes.get (j))
                    primes.set (j, false);
                j += (2 * i);
            }
        }
    }
    return primes;
}   

contestado el 22 de mayo de 12 a las 16:05

1000000000/32 matriz de elementos enteros... ¿no seguirá siendo mucho espacio? ¿Hay alguna forma de explotar la restricción de que nm <=100000 en este programa? ¡Gracias! - Rohit Agrawal

Es 1/32 GB, ¿no? Precio aproximado: medio dólar. Tendería a ver un problema en el tiempo necesario para llenar este conjunto de bits. Sin embargo, no sé cómo usar Eulers totient para resolver el problema y no veo ayuda en el intervalo de 100 000. No espero que se permita el método probablePrime. - usuario desconocido

¿te TENER almacenar el resultado en la matriz? ¿Qué tal un método que calcule si un entero dado es primo o no y a unos llámelo para cada número en {left,left+1,...,right} ?

contestado el 22 de mayo de 12 a las 15:05

No he inicializado la tabla isNotPrime ya que los valores predeterminados serán falsos, lo que pretendo tener. Además, el método que sugieres sería muy costoso en términos de tiempo y es por eso que no lo usé. - Rohit Agrawal

Probar cada número candidato por separado para la primalidad (¿por división de prueba?) es mucho más lento que usar el tamiz compensado de Eratóstenes para marcar los compuestos en todo el rango a la vez (para cada número primo debajo de sqrt del límite superior, por supuesto). - Will Ness

Siempre puede usar un desplazamiento al acceder a la matriz isNotPrime.

Dado m, n:

boolean[] isNotPrime = new boolean[n-m+1];

// to now if number x is primer or not
boolean xIsPrime = isNotPrime[x-m];

Aquí m es el desplazamiento.

contestado el 22 de mayo de 12 a las 15:05

No está obligado a tener una matriz grande: puede mantener una lista de los primos encontrados hasta ahora y probar usando múltiples matrices, con valores = array_slot + offset (valores ya probados). Una vez que haya terminado los valores de i a j, agregue ji para compensar y comience una nueva matriz a partir de J.

Puede eliminar números pares de su matriz, lo que le ahorraría algo de espacio (valores = array_slot * 2 - 1).

contestado el 22 de mayo de 12 a las 15:05

Dado que la distancia entre m y n es relativamente pequeña, puede usar la fuerza bruta y usar un algoritmo de prueba de primalidad rápido en cada número entre m y n.

Si permite algoritmos probabilísticos, puede usar Prueba de Miller-Rabin. Sean M = nm <= 10^5 y N = n <= 10^9. La complejidad del algoritmo de fuerza bruta sería O(k M (log N)^3), donde k es una constante que controla las garantías probabilísticas (para aplicaciones prácticas, k se puede establecer en 10).

Para los límites del problema, esta complejidad rondará los 10^9.

contestado el 22 de mayo de 12 a las 19:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.