← Все вопросы

Линейное решето Эйлера: как за O(n) посчитать наименьший простой делитель каждого числа?

Задан 21 месяц назад1.1к просмотров2 ответа
11

Решето Эратосфена за O(n log log n) понятно, но я слышал про линейное решето за чистую O(n), которое заодно даёт массив наименьших простых делителей (lp[i]) — это позволяет факторизовать любое i ≤ n за O(log i). Не могу понять, почему каждое составное вычёркивается ровно один раз. Дайте корректную реализацию и объяснение инварианта.

2 ответа

16
✓ Принятый ответ — помог автору

Идея линейного решета: каждое составное число x мы вычёркиваем ровно один раз — через представление x = lp[x] · m, где lp[x] — наименьший простой делитель. Это и даёт O(n): нет повторных вычёркиваний.

const int N = 1e7 + 5;
int lp[N];          // наименьший простой делитель i
vector<int> primes; // все простые до N

void linear_sieve(int n) {
    for (int i = 2; i <= n; i++) {
        if (lp[i] == 0) {        // i простое
            lp[i] = i;
            primes.push_back(i);
        }
        for (int p : primes) {
            if (p > lp[i] || (long long)i * p > n) break;
            lp[i * p] = p;
        }
    }
}

Ключевой момент — условие if (p > lp[i]) break;. Мы домножаем i только на простые p ≤ lp[i]. Тогда для числа i*p его наименьший простой делитель — это именно p (потому что p ≤ lp[i]), и оно будет помечено именно из пары (i, p), и больше никогда. Каждое составное число встречается ровно в одной такой паре → каждое вычёркивание уникально → O(n) по времени, O(n) по памяти.

Факторизация через lp потом тривиальна и работает за O(log x):

vector<int> factorize(int x) {
    vector<int> f;
    while (x > 1) { f.push_back(lp[x]); x /= lp[x]; }
    return f;
}
6

Стоит честно сказать про практику: линейное решето по константе часто проигрывает обычному Эратосфену, несмотря на лучшую асимптотику — у него хуже локальность кеша (random-доступ к lp[i*p]) и тратится память на массив primes. Бери линейное решето именно когда тебе нужен сам массив lp[] (факторизация запросов, мультипликативные функции вроде phi/mu за O(n)). Если нужно просто перечислить простые — обычное решето с битовым массивом обычно быстрее на практике.

Ваш ответ

Войдите, чтобы ответить на вопрос.