Программа для поиска всех простых чисел в очень большом заданном диапазоне целых чисел

Я столкнулся со следующим вопросом на веб-сайте программирования: Питер хочет сгенерировать несколько простых чисел для своей криптосистемы. Помоги ему! Ваша задача - сгенерировать все простые числа между двумя заданными числами!

Вход

Ввод начинается с количества t тестов в единственной строке (t ‹= 10). В каждой из следующих t строк есть два числа m и n (1 ‹= m‹ = n ‹= 1000000000, n-m‹ = 100000), разделенных пробелом.

Я пришел к следующему решению:

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;
                }
            }
        }
    }
}

Я отредактировал свое решение после прочтения некоторых предложений. Я все еще получаю сообщение об ошибке «Превышен лимит времени». Есть еще предложения о том, как еще больше оптимизировать это? Я вычисляю все простые числа до 32000, а затем использую их, чтобы найти простые числа от n до m.

Спасибо, Рохит


person Rohit Agrawal    schedule 22.05.2012    source источник
comment
Если вы начнете с 3, а не с 2 в сите, и сможете поменять местами значение на 2 вне цикла, вы можете выполнить итерацию по i + = 2 ;. Тогда, вместо того, чтобы работать с isNotPrime.length во внешнем цикле, достаточно √(isNotPrime.length). Несвязанный: у сканера есть метод nextInt.   -  person user unknown    schedule 22.05.2012
comment
Вы можете мгновенно сократить время выполнения вдвое, перебирая только коэффициенты в диапазоне, начиная с нечетного числа и увеличивая на i+=2 в for (int i = 0; i < answer.length; i+=2). Убедитесь, что i соответствует нечетному числу не ниже вашего left. Никакое четное число больше 2 никогда не будет простым. :) Это была бы разреженная схема адресации, еще быстрее будет работать со сжатым массивом, где запись с индексом i представляет собой число n=left_odd + 2i. Внутри Sieve() также работайте j+=2*i (хотя это сито очень маленькое), но, что более важно, внутри getAnswer(). Посмотрите на ответ Дэниела.   -  person Will Ness    schedule 23.05.2012


Ответы (6)


Тебе дали

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

это очень маленькие числа. Чтобы отсеять диапазон с верхней границей n, вам нужны простые числа до √n. Здесь вы знаете n <= 10^9, так что √n < 31623, так что в худшем случае вам понадобятся простые числа до 31621. Их 3401. Вы можете сгенерировать их с помощью стандартного сита за несколько микросекунд.

Затем вы можете просто просеять небольшой диапазон от m до n, отметив кратные числам, которые вы просеивали раньше, и остановитесь, когда простое число превысит √n. Некоторого ускорения можно добиться, исключив из сита кратные числа некоторых маленьких простых чисел, но логика становится более сложной (вам нужно обрабатывать сита с маленькими m специально).

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;
}

Использование BitSet или любого другого типа упакованного массива флагов уменьшит использование памяти и, таким образом, может дать значительное ускорение из-за лучшей локальности кеша.

person Daniel Fischer    schedule 22.05.2012
comment
Я отредактировал свое решение в соответствии с вашим предложением, но все еще получаю ситуацию превышения лимита времени. Не могли бы вы посоветовать, как дальше оптимизировать? - person Rohit Agrawal; 22.05.2012
comment
Какой срок и в чем задача? Если вам нужно распечатать все простые числа, это, вероятно, узкое место. Я плохо разбираюсь в вводе-выводе в Java, поэтому не могу дать совет, как ускорить печать. Если вы должны печатать только количество (или сумму) простых чисел в каждом интервале, это вряд ли будет проблемой. Эта реализация выполняет настройку и отсеивает 100 интервалов длиной примерно 100000 за менее 90 мс, здесь время запуска JVM больше. Конечно, тестовые машины могут быть медленнее и иметь меньшие кеши, поэтому замена boolean[] на BitSets может быть хорошей. - person Daniel Fischer; 22.05.2012

Используйте BitSet вместо массива 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;
}   
person user unknown    schedule 22.05.2012
comment
1000000000/32 целочисленный массив элементов .. разве это не будет много места? Есть ли способ использовать ограничение n-m ‹= 100000 в этой программе? Спасибо! - person Rohit Agrawal; 22.05.2012
comment
Это 1/32 ГБ, не так ли? Цена примерно: пол бакса. Я бы склонен увидеть проблему во времени, необходимом для заполнения этого битового набора. Однако я не знаю, как использовать тотализатор Эйлера для решения проблемы, и не вижу помощи в интервале 100 000. Я не ожидаю, что метод probablePrime разрешен. - person user unknown; 22.05.2012

Вы ДОЛЖНЫ сохранять результат в массиве? как насчет метода, который вычисляет, является ли данное целое число простым или нет, и просто вызывает его для каждого числа в {left,left+1,...,right}?

person snajahi    schedule 22.05.2012
comment
Я не инициализировал таблицу isNotPrime, так как по умолчанию будут установлены значения false, которые я намереваюсь иметь. Кроме того, метод, который вы предлагаете, был бы очень дорогим с точки зрения времени, и поэтому я не использовал его. - person Rohit Agrawal; 22.05.2012
comment
тестирование каждого числа кандидатов в разделении на простоту (пробным делением?) намного медленнее, чем использование сита Эратосфена для одновременной маркировки композитов по всему диапазону (для каждого простого числа ниже sqrt верхнего предела, конечно). - person Will Ness; 23.05.2012

Вы всегда можете использовать смещение при доступе к массиву isNotPrime.

Учитывая m, n:

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

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

Здесь m - смещение.

person Guillaume Polet    schedule 22.05.2012

Вы не обязаны иметь один большой массив: вы можете сохранить список найденных простых чисел и протестировать с использованием нескольких массивов, имеющих значения = array_slot + offset (уже проверенные значения). После того, как вы закончили значения от i до j, вы добавляете j-i к смещению и начинаете новый массив, начиная с J.

Вы можете удалить четные числа из своего массива, что сэкономит вам место (values ​​= array_slot * 2 - 1).

person Pyra    schedule 22.05.2012

Поскольку расстояние между m и n относительно невелико, вы можете перебрать и использовать быстрый алгоритм проверки простоты для каждого числа от m до n.

Если вы разрешаете вероятностные алгоритмы, вы можете использовать тест Миллера-Рабина. Пусть M = n-m ‹= 10 ^ 5 и N = n‹ = 10 ^ 9. Сложность алгоритма грубой силы будет O (k M (log N) ^ 3), где k - константа, которая контролирует вероятностные гарантии (для практических приложений k может быть установлено равным 10).

В рамках задачи эта сложность будет около 10 ^ 9.

person kunigami    schedule 22.05.2012