Простые числа
Мотивация и контекст
Простые числа — атомы арифметики. Основная теорема арифметики утверждает, что каждое натуральное число единственным образом раскладывается в произведение простых. Это делает простые числа неизбежными участниками почти каждой задачи по теории чисел.
В соревновательном программировании простые числа встречаются повсюду: проверка числа на простоту, генерация простых до заданной границы, работа с большими простыми в криптографических задачах. Без быстрых алгоритмов даже проверка одного числа на простоту с наивным подходом займёт операций — неприемлемо медленно.
Теория
Определение простого числа
Определение. Натуральное число называется простым, если оно не имеет делителей, кроме 1 и самого себя. Число, не являющееся простым и большее 1, называется составным.
Число 1 не является ни простым, ни составным по определению — это важное соглашение, необходимое для единственности разложения в произведение простых.
Теорема (Основная теорема арифметики). Каждое натуральное число представляется в виде произведения простых чисел, и это представление единственно с точностью до порядка множителей:
Доказательство (существование). Индукция по . База: — простое. Шаг: если простое, представление очевидно. Если составное, то , . По индукционному предположению, и раскладываются в произведения простых; перемножив, получаем разложение .
Доказательство (единственность). Предположим, — два разложения. Из и простоты следует для некоторого . Но — простое, значит . Сокращая и применяя индукцию, получаем и разложения совпадают.
Теорема о бесконечности простых
Теорема (Евклид, ~300 до н.э.). Простых чисел бесконечно много.
Доказательство. Предположим противное: все простые — это конечный список . Рассмотрим . Это число больше 1 и должно делиться на какое-то простое . Но тогда — противоречие.
Пробное деление
Теорема. Если составное, то у него есть простой делитель .
Доказательство. Пусть , . Тогда (иначе ). У есть простой делитель .
Следствие: для проверки числа на простоту достаточно проверить делимость на все числа от 2 до . Сложность: .
Оптимизация: проверяем 2 отдельно, затем только нечётные числа — ускорение в 2 раза. Ещё лучше — схема «2, 3 и числа вида »: простое число не делится ни на 2, ни на 3, значит имеет вид . Это даёт ускорение в 3 раза.
Решето Эратосфена
Алгоритм. Создаём массив флагов is_prime[0..n], изначально все true. Последовательно для каждого незачёркнутого начиная с 2 зачёркиваем все кратные Незачёркнутые числа — это простые.
Теорема. Решето Эратосфена выполняется за операций.
Доказательство. Для простого мы выполняем примерно операций зачёркивания. Суммируем по всем простым :
По теореме Мертенса, , где — константа Мейсселя-Мертенса. Значит, сумма .
Замечание. Решето за — практически линейное. Для оно работает за ~0.05 секунды, для — за ~0.5 секунды. Граница обычно достижима с оптимизациями.
Сегментированное решето
Классическое решето требует памяти, что неприемлемо для . Сегментированное решето решает эту проблему: оно находит простые на отрезке , используя памяти.
Алгоритм:
- Находим все простые классическим решетом.
- Разбиваем на блоки размера .
- Для каждого блока и каждого малого простого зачёркиваем кратные в этом блоке.
Сложность: — доминирует второй член.
Линейное решето (решето за O(n))
Классическое решето зачёркивает каждое составное число несколько раз. Линейное решето гарантирует, что каждое число зачёркивается ровно один раз — через своего наименьшего простого делителя (smallest prime factor, SPF).
Алгоритм:
- Поддерживаем список простых, найденных до сих пор.
- Для каждого от 2 до : если не помечено, оно простое. Для каждого простого из нашего списка (пока ) помечаем через .
Ключевой инвариант: зачёркивается именно через наименьший простой делитель произведения (так как , это самый маленький делитель). Каждое составное число обрабатывается ровно один раз.
Тест Миллера–Рабина
Для чисел пробное деление слишком медленно. Тест Миллера-Рабина — вероятностный тест на простоту, который с высокой вероятностью правильно определяет составные числа.
Малая теорема Ферма. Если — простое и , то .
К сожалению, обратное неверно: числа Кармайкла (например, 561 = 3·11·17) удовлетворяют условию Ферма для всех оснований, но составные. Тест Миллера-Рабина устраняет этот изъян.
Теорема (Тест Миллера-Рабина). Пусть нечётно, (где нечётно). Число — составное (свидетель — ), если:
- , И
- для всех .
Если ни одно из условий не выполнено, называется вероятно простым по основанию (или — сильный свидетель Ферма для ).
Доказательство корректности. Пусть простое. Тогда по малой теореме Ферма. Рассматриваем цепочку квадратных корней: . В этой цепочке последнее число равно 1. Значит, найдётся позиция, где при возведении в квадрат получается 1. Это возможно только если само значение было (так как уравнение в поле имеет только решения ). Значит, либо , либо для некоторого : . Условие выполнено. Контрапозитивно: если условие нарушено, — составное.
Детерминированный вариант. Для конкретных наборов оснований тест становится детерминированным для чисел до определённого предела:
| Предел | Достаточные основания |
|---|---|
Последний набор из 12 оснований покрывает все 64-битные числа (и даже больше — до ), что на практике означает полностью детерминированный тест для всех задач в соревновательном программировании.
Доказательство корректности детерминированного варианта опирается на результаты вычислительных проверок (Bach, Sorenson, и др.) — было явно проверено, что для чисел в соответствующих диапазонах нет псевдопростых для данного набора оснований.
Распределение простых чисел
Теорема (Теорема о простых числах, Адамар и де ла Валле Пуссен, 1896). Обозначим — количество простых, не превосходящих . Тогда:
Более точная аппроксимация — логарифмический интеграл . Практическое правило: для простых примерно , для — около .
Ключевые формулы
╔══════════════════════════════════════════════════════════════════╗
║ СВОДКА: ПРОСТЫЕ ЧИСЛА ║
╠══════════════════════════════════════════════════════════════════╣
║ Пробное деление: O(√n) ║
║ Решето Эратосфена: O(n log log n), память O(n) ║
║ Сегментированное решето: O(√R + (R−L) log log R), пам. O(√R) ║
║ Линейное решето (SPF): O(n) времени и памяти ║
║ Тест Миллера-Рабина: O(k log²n), k = число оснований ║
║ Детерминированный МР для n < 3.3×10²⁴: 12 оснований ║
║ π(n) ≈ n / ln n (теорема о простых числах) ║
║ n − 1 = 2^s · d (d нечётно) — разложение для теста МР ║
╚══════════════════════════════════════════════════════════════════╝
Реализация на C++20
#include <bits/stdc++.h> using namespace std; using ll = long long; using ull = unsigned long long; // ============================================================ // 1. Пробное деление — проверка числа на простоту за O(√n) // ============================================================ bool is_prime_trial(ll n) { if (n < 2) return false; if (n == 2 || n == 3) return true; if (n % 2 == 0 || n % 3 == 0) return false; // Проверяем числа вида 6k±1, начиная с 5 // 5, 7, 11, 13, 17, 19, 23, 25(=5²), ... for (ll i = 5; i * i <= n; i += 6) { if (n % i == 0 || n % (i + 2) == 0) return false; } return true; } // ============================================================ // 2. Классическое решето Эратосфена за O(n log log n) // Использует bitset для экономии памяти (~n/8 байт) // ============================================================ // Версия с vector<bool> — битовое кодирование автоматически vector<bool> sieve_basic(int n) { vector<bool> is_prime(n + 1, true); is_prime[0] = is_prime[1] = false; for (int i = 2; (ll)i * i <= n; i++) { if (is_prime[i]) { // Начинаем с i*i, т.к. меньшие кратные уже зачёркнуты for (int j = i * i; j <= n; j += i) is_prime[j] = false; } } return is_prime; } // Версия, возвращающая сами простые числа vector<int> sieve_primes(int n) { vector<bool> is_prime(n + 1, true); is_prime[0] = is_prime[1] = false; for (int i = 2; (ll)i * i <= n; i++) { if (is_prime[i]) { for (int j = i * i; j <= n; j += i) is_prime[j] = false; } } vector<int> primes; for (int i = 2; i <= n; i++) if (is_prime[i]) primes.push_back(i); return primes; } // ============================================================ // 3. Сегментированное решето для простых на [l, r] // Время: O(√r + (r-l) log log r), память: O(√r + (r-l)) // ============================================================ vector<ll> sieve_segment(ll l, ll r) { // Шаг 1: все простые до sqrt(r) int sq = (int)sqrt((double)r) + 1; vector<bool> small(sq + 1, true); small[0] = small[1] = false; for (int i = 2; i * i <= sq; i++) if (small[i]) for (int j = i * i; j <= sq; j += i) small[j] = false; vector<int> small_primes; for (int i = 2; i <= sq; i++) if (small[i]) small_primes.push_back(i); // Шаг 2: зачёркиваем в [l, r] // seg[i] отвечает за число l + i vector<bool> seg(r - l + 1, true); if (l == 1) seg[0] = false; // 1 не простое if (l == 0) { seg[0] = false; if (r >= 1) seg[1] = false; } for (int p : small_primes) { // Первое кратное p, которое >= l ll start = max((ll)p * p, ((l + p - 1) / p) * p); // Убеждаемся, что start != p (само p — простое) if (start == p) start += p; for (ll j = start; j <= r; j += p) seg[j - l] = false; } vector<ll> result; for (ll i = 0; i <= r - l; i++) if (seg[i]) result.push_back(l + i); return result; } // ============================================================ // 4. Линейное решето (SPF — smallest prime factor) // Время: O(n), Память: O(n) // Бонус: для каждого числа хранит его наименьший простой делитель, // что позволяет факторизовать любое n <= N за O(log n). // ============================================================ struct LinearSieve { int n; vector<int> spf; // spf[i] = наименьший простой делитель i vector<int> primes; // список простых в порядке возрастания LinearSieve(int n) : n(n), spf(n + 1, 0) { for (int i = 2; i <= n; i++) { if (spf[i] == 0) { // i не помечено => i простое spf[i] = i; primes.push_back(i); } // Помечаем i * p для простых p <= spf[i] for (int p : primes) { if (p > spf[i] || (ll)i * p > n) break; spf[i * p] = p; } } } bool is_prime(int x) const { return x >= 2 && spf[x] == x; } // Факторизация за O(log n): делим последовательно на spf vector<pair<int,int>> factorize(int x) const { vector<pair<int,int>> result; while (x > 1) { int p = spf[x], cnt = 0; while (x % p == 0) { x /= p; cnt++; } result.push_back({p, cnt}); } return result; } }; // ============================================================ // 5. Тест Миллера–Рабина — детерминированный вариант // Работает корректно для всех n < 3.3 × 10^24 // Время: O(k * log^2(n)), где k = 12 (число оснований) // ============================================================ // Умножение по модулю без переполнения через __int128 ll mulmod(ll a, ll b, ll m) { return (__int128)a * b % m; } // Быстрое возведение в степень по модулю ll powmod(ll base, ll exp, ll mod) { ll result = 1; base %= mod; if (base < 0) base += mod; while (exp > 0) { if (exp & 1) result = mulmod(result, base, mod); base = mulmod(base, base, mod); exp >>= 1; } return result; } // Один раунд теста Миллера-Рабина для основания a // Возвращает true, если n вероятно простое (или a не является свидетелем) bool miller_rabin_round(ll n, ll a, ll d, int s) { ll x = powmod(a, d, n); if (x == 1 || x == n - 1) return true; // вероятно простое for (int r = 1; r < s; r++) { x = mulmod(x, x, n); if (x == n - 1) return true; // вероятно простое } return false; // СОСТАВНОЕ — a является свидетелем } bool is_prime_miller_rabin(ll n) { if (n < 2) return false; if (n == 2 || n == 3 || n == 5 || n == 7) return true; if (n % 2 == 0 || n % 3 == 0) return false; // Представляем n-1 = 2^s * d ll d = n - 1; int s = 0; while (d % 2 == 0) { d /= 2; s++; } // Детерминированный набор оснований для n < 3.3 * 10^24 // Источник: https://miller-rabin.appspot.com/ for (ll a : {2LL, 3LL, 5LL, 7LL, 11LL, 13LL, 17LL, 19LL, 23LL, 29LL, 31LL, 37LL}) { if (a >= n) continue; // для маленьких n некоторые основания > n if (!miller_rabin_round(n, a, d, s)) return false; // СОСТАВНОЕ } return true; // ПРОСТОЕ } // ============================================================ // Пример: использование всех реализаций // ============================================================ int main() { ios::sync_with_stdio(false); cin.tie(nullptr); // Тест пробного деления cout << "is_prime(97) = " << is_prime_trial(97) << "\n"; // 1 cout << "is_prime(100) = " << is_prime_trial(100) << "\n"; // 0 // Решето до 30 auto primes = sieve_primes(30); cout << "Простые до 30: "; for (int p : primes) cout << p << " "; cout << "\n"; // 2 3 5 7 11 13 17 19 23 29 // Сегментированное решето: простые на [10^9, 10^9 + 100] ll L = 1000000000LL, R = L + 100; auto seg_primes = sieve_segment(L, R); cout << "Простые на [" << L << ", " << R << "]: "; for (ll p : seg_primes) cout << p << " "; cout << "\n"; // Линейное решето LinearSieve ls(50); cout << "SPF[12] = " << ls.spf[12] << "\n"; // 2 cout << "SPF[35] = " << ls.spf[35] << "\n"; // 5 auto factors = ls.factorize(360); cout << "360 = "; for (auto [p, e] : factors) cout << p << "^" << e << " "; cout << "\n"; // 2^3 3^2 5^1 // Тест Миллера-Рабина ll large_prime = 998244353; // простое ll large_composite = 999999937LL * 2; // составное cout << "998244353 простое: " << is_prime_miller_rabin(large_prime) << "\n"; // 1 cout << "1999999874 простое: " << is_prime_miller_rabin(large_composite) << "\n"; // 0 // Проверка числа Кармайкла: 561 = 3*11*17 // Пробное деление правильно распознаёт его как составное cout << "561 (Кармайкл) простое по МР: " << is_prime_miller_rabin(561) << "\n"; // 0 — правильно! return 0; }
Анализ сложности
| Алгоритм | Время | Память | Применение |
|---|---|---|---|
| Пробное деление | Одиночная проверка, малые | ||
| Решето Эратосфена | Все простые до | ||
| Сегментированное решето | Простые на , большие | ||
| Линейное решето | SPF, факторизация всех до | ||
| Тест Миллера–Рабина | Одиночная проверка, большие |
Разбор задачи 1 — Подсчёт простых (простая задача)
Постановка. Дано запросов, каждый — число (). Для каждого запроса выведите количество простых чисел, не превосходящих .
Идея. Заранее построим префиксные суммы по массиву решета. Тогда каждый запрос отвечается за .
#include <bits/stdc++.h> using namespace std; const int MAXN = 1e6 + 5; int pi[MAXN]; // pi[i] = количество простых <= i int main() { ios::sync_with_stdio(false); cin.tie(nullptr); vector<bool> is_prime(MAXN, true); is_prime[0] = is_prime[1] = false; for (int i = 2; i < MAXN; i++) { if (is_prime[i]) for (int j = 2*i; j < MAXN; j += i) is_prime[j] = false; // Префиксная сумма: pi[i] = pi[i-1] + (is_prime[i] ? 1 : 0) pi[i] = pi[i-1] + (is_prime[i] ? 1 : 0); } pi[1] = 0; pi[0] = 0; int T; cin >> T; while (T--) { int n; cin >> n; cout << pi[n] << "\n"; } return 0; }
Замечание. Решето запускаем один раз (), каждый запрос — . Итого .
Разбор задачи 2 — Простые на отрезке (средняя задача)
Постановка (CF 26A, аналог). Дано запросов, каждый — пара (, ). Для каждого запроса найдите все простые на .
Идея. Ключевое наблюдение: , то есть сегмент небольшой, хотя большое. Применяем сегментированное решето.
#include <bits/stdc++.h> using namespace std; using ll = long long; // Возвращает простые на [l, r] (оба включительно) vector<ll> segment_sieve(ll l, ll r) { // Предварительно: простые до sqrt(r) int sq = (int)sqrt((double)r) + 2; vector<bool> small_sieve(sq + 1, true); small_sieve[0] = small_sieve[1] = false; for (int i = 2; i * i <= sq; i++) if (small_sieve[i]) for (int j = i*i; j <= sq; j += i) small_sieve[j] = false; // Основное решето для отрезка [l, r] // Индекс j соответствует числу l + j int len = (int)(r - l + 1); vector<bool> seg(len, true); // Особый случай: 1 не простое if (l == 1) seg[0] = false; for (int p = 2; p <= sq; p++) { if (!small_sieve[p]) continue; // Первое кратное p, большее или равное l ll first = ((l + p - 1) / p) * p; if (first == p) first += p; // само p не зачёркиваем for (ll j = first; j <= r; j += p) seg[(int)(j - l)] = false; } vector<ll> result; for (int i = 0; i < len; i++) if (seg[i]) result.push_back(l + i); return result; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T; cin >> T; while (T--) { ll l, r; cin >> l >> r; auto primes = segment_sieve(l, r); for (ll p : primes) cout << p << "\n"; } return 0; }
Анализ. Для каждого запроса: на построение малого решета (можно вынести за цикл) и на сегмент. При запросах с общей суммой длин решение работает за доли секунды.
Важный нюанс. При вычислении first = ((l + p - 1) / p) * p возможно переполнение, если . Используйте вариант: first = (l / p) * p; if (first < l) first += p; — он безопаснее.
Разбор задачи 3 — Сложная задача уровня Div.1 D
Постановка (CF 1033G / аналог ICPC). Дано число (). Найдите наибольший простой делитель .
Обсуждение. Пробное деление за неприемлемо: для это операций. Нужен быстрый тест простоты () и быстрая факторизация ( — алгоритм Полларда, см. главу 3). Здесь сосредоточимся на части с тестом Миллера-Рабина.
Задача 3 (адаптированная). Дано чисел (). Для каждого числа определить, является ли оно простым.
#include <bits/stdc++.h> using namespace std; using ll = long long; ll mulmod(ll a, ll b, ll m) { return (__int128)a * b % m; } ll powmod(ll base, ll exp, ll mod) { ll result = 1; base %= mod; while (exp > 0) { if (exp & 1) result = mulmod(result, base, mod); base = mulmod(base, base, mod); exp >>= 1; } return result; } bool miller_rabin_round(ll n, ll a) { if (n % a == 0) return n == a; ll d = n - 1; int s = 0; while (d % 2 == 0) { d /= 2; s++; } ll x = powmod(a, d, n); if (x == 1 || x == n - 1) return true; for (int r = 1; r < s; r++) { x = mulmod(x, x, n); if (x == n - 1) return true; } return false; } bool is_prime(ll n) { if (n < 2) return false; for (ll a : {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}) { if (!miller_rabin_round(n, a)) return false; } return true; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T; cin >> T; while (T--) { ll n; cin >> n; cout << (is_prime(n) ? "YES" : "NO") << "\n"; } return 0; }
Более сложная задача с тем же инструментом (CF 1033G). Даны чисел, нужно найти среди них те, у которых наибольший простой делитель максимален. Алгоритм:
- Для каждого числа применить тест Миллера-Рабина (проверить, не простое ли оно само).
- Если составное — применить -алгоритм Полларда (глава 3) для полной факторизации.
- Выбрать максимальный простой делитель.
Тест Миллера-Рабина здесь — ключевой строительный блок, без которого -алгоритм не работает (он нужен для проверки, является ли найденный делитель простым).
Пример тестирования крайних случаев:
// Числа Кармайкла (псевдопростые по Ферма, но составные по МР): // 561 = 3 * 11 * 17 // 41041 = 7 * 11 * 13 * 41 // 825265 = 5 * 7 * 17 * 19 * 73 assert(!is_prime(561)); assert(!is_prime(41041)); // Большие простые: assert(is_prime(999999999999999877LL)); // простое assert(is_prime(998244353LL)); // простое (известное из NTT) // Квадрат большого простого: assert(!is_prime(999999999999999877LL * 2)); // составное (переполнение?) // Осторожно! 999999999999999877 * 2 переполняет long long! // Нужно проверять: is_prime(n) и само n вписывается в ll.
Задачи для самостоятельного решения
| Задача | Источник | Сложность |
|---|---|---|
| Проверка числа на простоту () | Базовая | ★ |
| Количество простых на , | CF 2B | ★ |
| Простые-близнецы до : пары | Базовая | ★ |
| Простые на , , | CF 26A | ★★ |
| Наименьшее простое , | Базовая + МР | ★★ |
| Сумма простых на , большой диапазон | CF 569B | ★★ |
| Для скольких число делится на | CF 1033D | ★★★ |
| Количество чисел в с ровно простыми делителями | CF 548E | ★★★ |
| Простые числа специального вида (Мерсенна, Ферма) | ICPC 2019 | ★★★★ |
Типичные ошибки
Ошибка 1: Переполнение в решете при вычислении i * i.
// НЕПРАВИЛЬНО для i ~ 50000, j ~ 2.5*10^9 > INT_MAX for (int i = 2; i * i <= n; i++) { ... } // i*i может переполниться! // ПРАВИЛЬНО: использовать long long для сравнения for (int i = 2; (long long)i * i <= n; i++) { ... } // или: for (int i = 2; i <= (int)sqrt(n); i++) { ... } // но sqrt неточен! // Самый надёжный вариант: (ll)i * i <= (ll)n
Ошибка 2: Начало зачёркивания не с i*i.
// НЕПРАВИЛЬНО: медленно — начинаем с 2*i, хотя 2*i, 3*i, ..., (i-1)*i // уже зачёркнуты на предыдущих итерациях for (int j = 2 * i; j <= n; j += i) is_prime[j] = false; // ПРАВИЛЬНО: начинаем с i*i for (int j = i * i; j <= n; j += i) is_prime[j] = false;
Ошибка 3: Сегментированное решето — неправильное начало для p.
// Нужно НЕ зачёркивать само p (оно простое!) ll first = ((l + p - 1) / p) * p; if (first == p) first += p; // ← эта строка обязательна!
Ошибка 4: Тест Миллера-Рабина — забытый mulmod.
// НЕПРАВИЛЬНО: (base * base) переполняется для base ~ 10^9 x = x * x % n; // ПРАВИЛЬНО: x = (__int128)x * x % n; // или через mulmod(x, x, n)
Ошибка 5: Неправильная обработка маленьких чисел в тесте МР.
// Если a >= n, тест для основания a не имеет смысла! for (ll a : {2, 3, 5, 7, ...}) { if (a >= n) continue; // ОБЯЗАТЕЛЬНО пропускаем ... }
Ошибка 6: Использование вероятностного МР в конкурсе без детерминированных оснований. Если использовать только малый набор оснований (например, только {2, 7, 61}), задача может быть специально составлена с числом, проходящим случайный тест. Всегда используйте детерминированный набор из 12 оснований.
Совет профессионала
Тест Миллера-Рабина с 12 основаниями детерминирован для , что перекрывает все 64-битные числа. Однако у этого подхода есть малоизвестный враг — переполнение __int128. Числа , а при вычислении использует числа до , что вписывается в unsigned __int128 (максимум ~), но не в знаковый __int128 (максимум ~). Используйте unsigned __int128 или явный mulmod с __int128.
Ещё более продвинутый приём: на процессорах x86-64 с поддержкой инструкции mulx компилятор может автоматически оптимизировать (__int128)a * b % m до двух машинных инструкций вместо вызова функции. Для максимальной скорости тестируйте с флагами -O2 -march=native.
// Быстрый mulmod через встроенный __uint128_t (предпочтительно) inline ll mulmod(ll a, ll b, ll m) { return (unsigned __int128)a * b % m; }
Итог
Простые числа — фундаментальный объект математики и программирования. Мы изучили весь спектр алгоритмов — от пробного деления до теста Миллера-Рабина, способного проверить любое 64-битное число менее чем за микросекунду.
Ключевые выводы:
- Пробное деление () достаточно для .
- Решето Эратосфена () — стандарт для генерации всех простых до .
- Сегментированное решето позволяет работать с большими , имея лишь памяти.
- Линейное решето за даёт не только простые, но и SPF для мгновенной факторизации.
- Тест Миллера-Рабина с 12 детерминированными основаниями покрывает весь диапазон — используйте его для больших чисел.