Жай сан деген не?
Жай сан — 1-ден үлкен және тек 1-ге және өзіне ғана бөлінетін сан.
Жай сандар мысалы:
2 3 5 7 11 13 17 19
Мысалы, 7 жай сан, себебі ол тек 1 және 7-ге бөлінеді.
Құрама сан деген не?
Құрама сан — 1-ден үлкен және 1 мен өзінен басқа бөлгіші бар сан.
Құрама сандар мысалы:
4 6 8 9 10 12 14 15
Мысалы, 12 құрама сан, себебі ол мына сандарға бөлінеді:
1 2 3 4 6 12
Задача
n саны берілген. 1-ден n-ге дейінгі барлық жай сандарды табу керек.
Input
20
Output
2 3 5 7 11 13 17 19
Баяу әдіс
Әр санды жеке-жеке тексеруге болады: жай ма, жай емес пе.
x санын тексеру:
bool is_prime(int x) {
if (x < 2) {
return false;
}
for (int d = 2; d * d <= x; d++) {
if (x % d == 0) {
return false;
}
}
return true;
}
Егер барлық санды 1-ден n-ге дейін осылай тексерсек:
Эратосфен решетосының идеясы
Әр санды бөлек тексермейміз. Оның орнына құрама сандарды белгілеп шығамыз.
Идея:
2. Бірінші жай сан p аламыз
3. p-ның барлық кратный сандарын құрама деп белгілейміз
4. Келесі белгіленбеген санға өтеміз
Мысалы, егер p = 2, онда белгілейміз:
4 6 8 10 12 14 16 ...
Егер p = 3, онда белгілейміз:
6 9 12 15 18 21 ...
Не сақтаймыз?
Массив құрамыз:
isPrime[i]
Мағынасы:
- true — i саны әзірше жай деп саналады
- false — i саны құрама
Басында барлық санды жай деп қоямыз:
vector<bool> isPrime(n + 1, true);
Бірақ 0 және 1 жай сан емес:
isPrime[0] = false;
isPrime[1] = false;
Негізгі цикл
p санын перебор жасаймыз. Егер ол әлі жай болса, оның кратный сандарын белгілейміз.
for (int p = 2; p * p <= n; p++) {
if (isPrime[p]) {
for (int x = p * p; x <= n; x += p) {
isPrime[x] = false;
}
}
}
Неге p * p-дан бастаймыз?
Мысалы, p = 5. 5-тің кратный сандары:
10 15 20 25 30 35 ...
Бірақ:
- 10 санын 2 белгілеп қойған
- 15 санын 3 белгілеп қойған
- 20 санын 2 белгілеп қойған
5 үшін нақты басталатын бірінші жаңа сан:
25 = 5 * 5
Неге сыртқы цикл sqrt(n)-ге дейін?
Егер x құрама сан болса, оның бір бөлгіші міндетті түрде sqrt(x)-тен аспайды.
Сондықтан n-ге дейінгі барлық құрама санды белгілеу үшін p-ны тек sqrt(n)-ге дейін қарау жеткілікті.
p * p <= n
Осыдан кейін белгіленбей қалған сандар жай болады.
Эратосфен решетосының толық реализациясы
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<bool> isPrime(n + 1, true);
if (n >= 0) {
isPrime[0] = false;
}
if (n >= 1) {
isPrime[1] = false;
}
for (int p = 2; p * p <= n; p++) {
if (isPrime[p]) {
for (int x = p * p; x <= n; x += p) {
isPrime[x] = false;
}
}
}
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
cout << i << ' ';
}
}
return 0;
}
Input / Output
Input
30
Output
2 3 5 7 11 13 17 19 23 29
Решето қолмен қалай жұмыс істейді?
n = 20 болсын.
Басында:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
2-ні аламыз, кратный сандарын белгілейміз:
4 6 8 10 12 14 16 18 20
3-ті аламыз, кратный сандарын белгілейміз:
9 15
5-ке келгенде: 5 * 5 = 25, ол 20-дан үлкен.
Қалған жай сандар:
2 3 5 7 11 13 17 19
Жай сандар санын табу коды
Кейде жай сандарды шығармай, тек санын табу керек.
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
cnt++;
}
}
cout << cnt;
Толық код: n-ге дейін қанша жай сан бар?
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<bool> isPrime(n + 1, true);
if (n >= 0) {
isPrime[0] = false;
}
if (n >= 1) {
isPrime[1] = false;
}
for (int p = 2; p * p <= n; p++) {
if (isPrime[p]) {
for (int x = p * p; x <= n; x += p) {
isPrime[x] = false;
}
}
}
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
cnt++;
}
}
cout << cnt;
return 0;
}
Жай сандарды vector ішіне сақтау
Көбіне барлық жай сандарды массивке сақтап алу ыңғайлы:
vector<int> primes;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes.push_back(i);
}
}
Кейін primes басқа задачаларда қолданылады.
sieve функциясы
Решетоны бөлек функцияға шығарған ыңғайлы.
vector<int> sieve(int n) {
vector<bool> isPrime(n + 1, true);
vector<int> primes;
if (n >= 0) {
isPrime[0] = false;
}
if (n >= 1) {
isPrime[1] = false;
}
for (int p = 2; p * p <= n; p++) {
if (isPrime[p]) {
for (int x = p * p; x <= n; x += p) {
isPrime[x] = false;
}
}
}
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes.push_back(i);
}
}
return primes;
}
long long-пен қауіпсіз вариант
Егер n үлкен болса, p * p int ішінде overflow беруі мүмкін.
Сондықтан қауіпсіз жазу:
1LL * p * p <= n
Ішкі циклдің стартын да былай жазамыз:
for (long long x = 1LL * p * p; x <= n; x += p)
Қауіпсіз реализация
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<bool> isPrime(n + 1, true);
if (n >= 0) {
isPrime[0] = false;
}
if (n >= 1) {
isPrime[1] = false;
}
for (int p = 2; 1LL * p * p <= n; p++) {
if (isPrime[p]) {
for (long long x = 1LL * p * p; x <= n; x += p) {
isPrime[x] = false;
}
}
}
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
cout << i << ' ';
}
}
return 0;
}
Smallest Prime Factor
Кейде сан жай ма емес пе ғана емес, сандарды тез простые множители-ге жіктеу керек болады.
Онда мына массивті санаймыз:
spf[x]
spf[x] — smallest prime factor, яғни x санының ең кіші жай бөлгіші.
spf[15] = 3
spf[17] = 17
SPF коды
#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
int spf[N + 1];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
for (int i = 0; i <= N; i++) {
spf[i] = i;
}
for (int p = 2; p * p <= N; p++) {
if (spf[p] == p) {
for (int x = p * p; x <= N; x += p) {
if (spf[x] == x) {
spf[x] = p;
}
}
}
}
int x;
cin >> x;
while (x > 1) {
cout << spf[x] << ' ';
x /= spf[x];
}
return 0;
}
SPF мысалы
Input
60
Output
2 2 3 5
Себебі:
60 = 2 * 2 * 3 * 5
Көп query үшін решето
Кейде көп запрос болады: әр сан жай ма, жоқ па тексеру керек.
Онда решетоны бір рет жасап аламыз, кейін әр query-ге O(1) жауап береміз.
if (isPrime[x]) {
cout << "YES";
} else {
cout << "NO";
}
Query-мен толық код
#include <bits/stdc++.h>
using namespace std;
const int N = 1000000;
bool isPrime[N + 1];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
for (int i = 0; i <= N; i++) {
isPrime[i] = true;
}
isPrime[0] = false;
isPrime[1] = false;
for (int p = 2; p * p <= N; p++) {
if (isPrime[p]) {
for (int x = p * p; x <= N; x += p) {
isPrime[x] = false;
}
}
}
int q;
cin >> q;
while (q--) {
int x;
cin >> x;
if (isPrime[x]) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}
return 0;
}
Асимптотика
| Метод | Уақыт | Память |
|---|---|---|
| Әр санды бөлек тексеру | O(n√n) | O(1) |
| Эратосфен решетосы | O(n log log n) | O(n) |
| Решетодан кейін query answer | O(1) | O(n) |
Решетоны қашан қолданамыз?
2. isPrime[x] query-леріне тез жауап беру керек
3. Көп санды множительдерге жіктеу керек
4. n-ге дейін жай сандар санын табу керек
5. primes list-ін басқа задачада қолдану керек
Типтік қателер
- 1 санын жай сан деп ойлау
- isPrime[0] = false қоюды ұмыту
- isPrime[1] = false қоюды ұмыту
- Ішкі циклді p * p орнына 2 * p-дан бастау
- p * p ішінде overflow алу
- Тек бір кішкентай сан тексеру керек кезде решето қолдану
- Массив size-ын тым кішкентай жасау
Қысқа шаблон
vector<bool> isPrime(n + 1, true);
if (n >= 0) {
isPrime[0] = false;
}
if (n >= 1) {
isPrime[1] = false;
}
for (int p = 2; 1LL * p * p <= n; p++) {
if (isPrime[p]) {
for (long long x = 1LL * p * p; x <= n; x += p) {
isPrime[x] = false;
}
}
}