Konu Başlıkları Gizle
Merhaba,
Bu yazım, bir öncekinin devamı niteliğinde: Rehber: Asal sayıları hızlı bulma: Sieve of Eratosthenes.
Yine Sieve of Eratosthenes yerine Sieve diye hitap edeceğim . Öncesinde sadece Sieve'in genel mantığını aktarmaya çalışmış, üstüne bir de olabildiğince hızlandırabilmenin yollarını göstermiştim; fakat Sieve'i kullanım örneklerim,
Anlatılacak pek bir şey yok. Sieve döngüsü içerisinde her asal bir sayıya denk gelişimizde o sayıyı, öncesinde tanımladığımız bir listeye (
[CODE lang="cpp" title="Asal listesi oluşturma"]#include <bits/stdc++.h>
using namespace std;
int main() {
const int n = 1e8;
vector<bool> asal_mi(n + 1, 1);
vector<int> asallar;
for (int i = 2; i <= n; i++) {
if (!asal_mi)
continue;
asallar.push_back(i);
for (int k = 2 * i; k <= n; k += i)
asal_mi[k] = false;
}
cout << "Asal sayisi: " << asallar.size() << "\n";
cout << "En kucuk asal: " << asallar.front() << "\n";
cout << "Medyan asal: " << asallar[asallar.size() / 2] << "\n";
cout << "En buyuk asal: " << asallar.back() << "\n";
}[/CODE]
Ideone testi (1.38 saniye): Ideone.com
Peki nasıl?
Peki
Şöyle düşünebiliriz:
Tıpkı faktöriyel formülü gibi (o da recursive):
Devam etmeden önce spoiler'ı okuduğunuzdan emin olun. 
Sonrasında
Biraz düşünün isterseniz, pratik ve eğlenceli burası:
İmplementasyonunu da paylaşayım artık:
[CODE lang="cpp" title="[l, r] aralığındaki asalların sayısını bulma kodu"]#include <bits/stdc++.h>
using namespace std;
int main() {
const int n = 1e8;
vector<bool> asal_mi(n + 1, 1);
vector<int> asal_sayisi(n + 1);
for (int i = 2; i <= n; i++) {
asal_sayisi = asal_sayisi[i - 1] + asal_mi;
if (!asal_mi)
continue;
for (int k = 2 * i; k <= n; k += i)
asal_mi[k] = false;
}
auto araliktaki_asal_sayisi = [&](int l, int r) {
l = max(l, 1);
return asal_sayisi[r] - asal_sayisi[l - 1];
};
cout << "[1, " << n << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(1, n) << "\n";
// https://codeforces.com/blog/entry/61587
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
for (int ornek = 0; ornek < 5; ornek++) {
int l = uniform_int_distribution<int>(1, n)(rng);
int r = uniform_int_distribution<int>(l, n)(rng);
cout << "[" << l << ", " << r << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(l, r) << "\n";
}
}[/CODE]
Ideone testi (1.76 saniye, ~400 MB hafıza): Ideone.com
[CODE lang="cpp" title="[l, r] aralığındaki asalların sayısını bulma (az hafıza kullanımı)"]#include <bits/stdc++.h>
using namespace std;
int main() {
const int n = 1e8;
vector<bool> asal_mi(n + 1, 1);
vector<int> asallar;
for (int i = 2; i <= n; i++) {
if (!asal_mi)
continue;
asallar.push_back(i);
for (int k = 2 * i; k <= n; k += i)
asal_mi[k] = false;
}
auto araliktaki_asal_sayisi = [&](int l, int r) {
l = max(l, 1);
return upper_bound(asallar.begin(), asallar.end(), r) - lower_bound(asallar.begin(), asallar.end(), l);
};
cout << "[1, " << n << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(1, n) << "\n";
// https://codeforces.com/blog/entry/61587
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
for (int ornek = 0; ornek < 5; ornek++) {
int l = uniform_int_distribution<int>(1, n)(rng);
int r = uniform_int_distribution<int>(l, n)(rng);
cout << "[" << l << ", " << r << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(l, r) << "\n";
}
}[/CODE]
Ideone testi (1.58 saniye, ~50 MB hafıza): Ideone.com (Çalışma süresi, hafıza farkından dolayı azalmış olmalı. Ayrıca ufak tefek dalgalanmalar da olabiliyor. Sorgu sayısı 5'ten çok daha fazla olsa ilk yöntem daha hızlı olurdu.)
Sorgu sayısı 5'ten 10 milyona çıktığında:
Normalde bir
[CODE lang="cpp" title="Kök(n)'de asal çarpanlara ayırma"]// (Asal, üssü) listesi döndürüyor.
vector<pair<long long, int>> carpanlara_ayir(long long n) {
vector<pair<long long, int>> asal_carpan_kuvvetleri;
for (long long bolen = 2; bolen * bolen <= n; bolen++) {
if (n % bolen)
continue;
// bolen kesinlikle bir asaldır, nedenini düşünebilirsiniz. Merak ederseniz sorabilirsiniz
int us = 0; // Asal üssü
while (n % bolen == 0) {
n /= bolen;
us++;
}
asal_carpan_kuvvetleri.push_back({bolen, us});
}
// En büyük asal çarpanın üssü 1 ise:
if (n > 1) {
asal_carpan_kuvvetleri.push_back({n, 1});
}
return asal_carpan_kuvvetleri;
}[/CODE]
Burada köke kadarki tüm sayıları gezmek zorunda kalıyoruz, oysaki asal olmayanları atlasak çok iyi olacak. 2'ye ayrı müdahale edip sonra döngüyü tek sayılar üzerinden de ilerletmek biraz hızlandırıyor ama en hızlı yöntem, asal sayı listesi üzerinde gezmek:
[CODE lang="cpp" title="Asal listesiyle asal çarpanlara ayırma"]// Sieve'i en az kök(n)'e kadar yapmanız gerekiyor.
// Mesela Sieve'i 10^8'e kadar yaptıysanız n en fazla 10^16 olmalı.
vector<pair<long long, int>> carpanlara_ayir(long long n, const vector<int>& asallar) {
vector<pair<long long, int>> asal_carpan_kuvvetleri;
for (long long bolen : asallar) {
// Bu da güzel optimize eder, özellikle çok büyük asal çarpan içermeyen sayılar için.
if (bolen * bolen > n)
break;
if (n % bolen)
continue;
int us = 0; // Asal üssü
while (n % bolen == 0) {
n /= bolen;
us++;
}
asal_carpan_kuvvetleri.push_back({bolen, us});
}
// En büyük asal çarpanın üssü 1 ise:
if (n > 1) {
asal_carpan_kuvvetleri.push_back({n, 1});
}
return asal_carpan_kuvvetleri;
}[/CODE]
Aralıktaki asal sayıların sayısını bulmanın 2. yönteminde asal sayıların oranından bahsetmiştim, tekrar bahsedeyim:
2. Yöntem:
Bu yöntem çok hızlı olmasına karşın bir önceki yöntemdeki gibi Sieve'in üst sınırının karesine kadarkiler değil, sadece üst sınıra kadarki sayıları asal çarpanlarına ayırabiliyor; ama bunu çok hızlı yapabiliyor.
Şöyle ki: Her sayının en küçük asal çarpanını bildiğinizi düşünün. Bu bilgiyle bir sayıyı sürekli en küçük asal çarpanına böle böle 1'e kadar giderseniz ve bu süreçte böldüğünüz tüm asal çarpanları tutarsanız aslında sayının tüm asal çarpanlarını elde edersiniz. İşin güzel yanı, bir
Bu yöntemde Sieve mantığından yararlanarak bir liste oluşturmamız gerekecek:
) Mantık şu:
En küçük asal çarpanları bulma kodunu paylaşayım:
[CODE lang="cpp" title="En küçük asal çarpanları hesaplama fonksiyonu"]vector<int> en_kucuk_asal_carpan_listesi(long long n) {
// asal_mi listesini kullanmamıza gerek yok
vector<int> en_kucuk_asal_carpan(n + 1); // İlk değer 0 olarak alınır, bir şey yazılmazsa.
for (int i = 2; i <= n; i++) {
// En küçük asal çarpanı varsa i asal değildir.
if (en_kucuk_asal_carpan)
continue;
// Bir asalın en küçük asal çarpanı kendisidir.
en_kucuk_asal_carpan = i;
for (int k = 2 * i; k <= n; k += i) {
// Henüz atanmamışsa k'nın en küçük asal çarpanı i'dir.
if (!en_kucuk_asal_carpan[k])
en_kucuk_asal_carpan[k] = i;
}
}
return en_kucuk_asal_carpan;
}[/CODE]
Şimdi de bu listeyi kullanarak
[CODE lang="cpp" title="En hızlı asal çarpanlara ayırma kodu"]// Asalın int'i aşacak hali yok, üst sınırdan dolayı. O yüzden long long kullanmadım.
vector<pair<int, int>> carpanlara_ayir(int n, const vector<int> &en_kucuk_asal_carpan) {
vector<pair<int, int>> asal_carpan_kuvvetleri;
// Sayıyı 1 oluncaya kadar sürekli en küçük asal çarpanına bölüyoruz
// ve bu esnada asal carpanları hafızada tutuyoruz.
while (n > 1) {
int asal_carpan = en_kucuk_asal_carpan[n];
int us = 0;
while (n % asal_carpan == 0) {
n /= asal_carpan;
us++;
}
asal_carpan_kuvvetleri.push_back({asal_carpan, us});
}
return asal_carpan_kuvvetleri;
}[/CODE]
Not: 1. yöntemde
Sieve'in kendisinin, direkt kendisi değilse bile mantığının (katları gezme) kullanıldığı daha birçok örnek mevcut; ama ben temel ve temel olduğu kadar önemli 1-2 tanesine değinmek istedim.
Okuduğunuz için teşekkür ederim. Umarım eğlenmişsinizdir.
Bu yazım, bir öncekinin devamı niteliğinde: Rehber: Asal sayıları hızlı bulma: Sieve of Eratosthenes.
Yine Sieve of Eratosthenes yerine Sieve diye hitap edeceğim . Öncesinde sadece Sieve'in genel mantığını aktarmaya çalışmış, üstüne bir de olabildiğince hızlandırabilmenin yollarını göstermiştim; fakat Sieve'i kullanım örneklerim,
1'den n'e kadarki asal sayıların sayısını bulmaktan öteye gitmemişti. İşte bu sefer kullanım örneklerini çeşitlendireceğim. İyi okumalar. Asal sayılar listesi oluşturma
Bunu aslında bir kullanım örneği olarak sunmaya layık görmemiştim; ama sonraki örnekte kullandığım için neden olmasın?Anlatılacak pek bir şey yok. Sieve döngüsü içerisinde her asal bir sayıya denk gelişimizde o sayıyı, öncesinde tanımladığımız bir listeye (
vector) ekleyebiliriz:[CODE lang="cpp" title="Asal listesi oluşturma"]#include <bits/stdc++.h>
using namespace std;
int main() {
const int n = 1e8;
vector<bool> asal_mi(n + 1, 1);
vector<int> asallar;
for (int i = 2; i <= n; i++) {
if (!asal_mi)
continue;
asallar.push_back(i);
for (int k = 2 * i; k <= n; k += i)
asal_mi[k] = false;
}
cout << "Asal sayisi: " << asallar.size() << "\n";
cout << "En kucuk asal: " << asallar.front() << "\n";
cout << "Medyan asal: " << asallar[asallar.size() / 2] << "\n";
cout << "En buyuk asal: " << asallar.back() << "\n";
}[/CODE]
Ideone testi (1.38 saniye): Ideone.com
Herhangi bir aralıktaki asalların sayısını bulma
Sadece[1, n] aralığıyla sınırlı kalmayabilir, herhangi bir [l, r] (1 <= l <= r <= n) aralığındaki asalların sayısını, öncesinde gerekli işlemleri yaparak (preprocessing; ön işleme) tek işlemde bulabiliriz.Peki nasıl?
1. Yöntem
İlk olarak ön işlemi düşünmemiz gerekiyor. Şimdi, Sieve ile[1, n] aralığındaki her bir sayının asal olup olmadığının işaretlenmiş olduğu bir boolean liste üretebiliyoruz (asal_mi). Örneğin asal_mi[3] == true, asal_mi[100000] == false oluyor. Bu listeyi kullanarak bir asal_sayisi listesi oluşturabiliriz, burada asal_sayisi[i], 1'den i sayısına kadarki asalların sayısını temsil eder. Mesela asal_sayisi[n] direkt 1'den n'e kadarki asalların sayısı olur.Peki
asal_sayisi listesini nasıl oluşturmamız gerekiyor?Şöyle düşünebiliriz:
i - 1'e kadarki asalların sayısını bilirsek i'ye kadarki asalların sayısı, i - 1'e kadar olanların üstüne i'nin asallığına bağlı olarak ya aynı kalır ya da bir fazla olur. Bundan dolayı asal_sayisi[i] = asal_sayisi[i - 1] + asal_mi[i] recursive (özyinelemeli) formülünü kullanabiliriz.Tıpkı faktöriyel formülü gibi (o da recursive):
i! = (i - 1)! * i.Sonrasında
asal_sayisi listesini [l, r] aralığındaki asal sayıların sayısını bulmak için nasıl kullanacağız?Biraz düşünün isterseniz, pratik ve eğlenceli burası:
[l, r] aralığındaki asalların sayısı, r'ye kadarki asalların sayısı eksi l - 1'e kadarkilerin sayısına denktir: asal_sayisi[r] - asal_sayisi[l - 1].İmplementasyonunu da paylaşayım artık:
[CODE lang="cpp" title="[l, r] aralığındaki asalların sayısını bulma kodu"]#include <bits/stdc++.h>
using namespace std;
int main() {
const int n = 1e8;
vector<bool> asal_mi(n + 1, 1);
vector<int> asal_sayisi(n + 1);
for (int i = 2; i <= n; i++) {
asal_sayisi = asal_sayisi[i - 1] + asal_mi;
if (!asal_mi)
continue;
for (int k = 2 * i; k <= n; k += i)
asal_mi[k] = false;
}
auto araliktaki_asal_sayisi = [&](int l, int r) {
l = max(l, 1);
return asal_sayisi[r] - asal_sayisi[l - 1];
};
cout << "[1, " << n << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(1, n) << "\n";
// https://codeforces.com/blog/entry/61587
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
for (int ornek = 0; ornek < 5; ornek++) {
int l = uniform_int_distribution<int>(1, n)(rng);
int r = uniform_int_distribution<int>(l, n)(rng);
cout << "[" << l << ", " << r << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(l, r) << "\n";
}
}[/CODE]
Ideone testi (1.76 saniye, ~400 MB hafıza): Ideone.com
2. Yöntem
Bunu yapmanın bir de hafızayı daha az kullanan ama biraz daha yavaş bir yolu var:asal_sayisi listesi oluşturmak yerine asalları bir listede toplayıp bu liste üzerinde binary search uygulamak. Hafıza kullanımı az çünkü asal sayıların oranı yaklaşık 1/ln(n) civarında. Bunun detayına çok girmek istemiyorum, sadece kodunu paylaşacağım:[CODE lang="cpp" title="[l, r] aralığındaki asalların sayısını bulma (az hafıza kullanımı)"]#include <bits/stdc++.h>
using namespace std;
int main() {
const int n = 1e8;
vector<bool> asal_mi(n + 1, 1);
vector<int> asallar;
for (int i = 2; i <= n; i++) {
if (!asal_mi)
continue;
asallar.push_back(i);
for (int k = 2 * i; k <= n; k += i)
asal_mi[k] = false;
}
auto araliktaki_asal_sayisi = [&](int l, int r) {
l = max(l, 1);
return upper_bound(asallar.begin(), asallar.end(), r) - lower_bound(asallar.begin(), asallar.end(), l);
};
cout << "[1, " << n << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(1, n) << "\n";
// https://codeforces.com/blog/entry/61587
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
for (int ornek = 0; ornek < 5; ornek++) {
int l = uniform_int_distribution<int>(1, n)(rng);
int r = uniform_int_distribution<int>(l, n)(rng);
cout << "[" << l << ", " << r << "] araligindaki asal sayisi: " << araliktaki_asal_sayisi(l, r) << "\n";
}
}[/CODE]
Ideone testi (1.58 saniye, ~50 MB hafıza): Ideone.com (Çalışma süresi, hafıza farkından dolayı azalmış olmalı. Ayrıca ufak tefek dalgalanmalar da olabiliyor. Sorgu sayısı 5'ten çok daha fazla olsa ilk yöntem daha hızlı olurdu.)
Sorgu sayısı 5'ten 10 milyona çıktığında:
- İlk yöntemin testi (2.85 saniye): Ideone.com
- İkinci yöntemin testi (7.27 saniye): Ideone.com
Hızlı asal çarpanlara ayırma
1. Yöntem: Asal sayılar listesini kullanma
Bir önceki rehberde, bir sayının asallığını kontrol etmek için sayının köküne kadar bakmanın yeterli olduğunu çünkü sayının kökünden büyük bir böleni varsa bir de onu tamlayan, kökünden küçük bir böleni olduğunu belirtmiştim. Bu yöntemde de bu özelliği kullanacağız. Bu yöntem, Sieve'in üst sınırının karesine kadarki sayıları asal çarpanlarına ayırabiliyor çünkü demin bahsettiğim durumdan ötürü üst sınırın karesine kadarki sayıların, Sieve'in bulduğu en büyük asal sayıdan büyük asal çarpanı olamaz.Normalde bir
n sayısını asal çarpanlarına en kötü durumda kök(n) operasyonla, sayının köküne kadar kontrol yaparak ayırabiliyoruz:[CODE lang="cpp" title="Kök(n)'de asal çarpanlara ayırma"]// (Asal, üssü) listesi döndürüyor.
vector<pair<long long, int>> carpanlara_ayir(long long n) {
vector<pair<long long, int>> asal_carpan_kuvvetleri;
for (long long bolen = 2; bolen * bolen <= n; bolen++) {
if (n % bolen)
continue;
// bolen kesinlikle bir asaldır, nedenini düşünebilirsiniz. Merak ederseniz sorabilirsiniz
int us = 0; // Asal üssü
while (n % bolen == 0) {
n /= bolen;
us++;
}
asal_carpan_kuvvetleri.push_back({bolen, us});
}
// En büyük asal çarpanın üssü 1 ise:
if (n > 1) {
asal_carpan_kuvvetleri.push_back({n, 1});
}
return asal_carpan_kuvvetleri;
}[/CODE]
Burada köke kadarki tüm sayıları gezmek zorunda kalıyoruz, oysaki asal olmayanları atlasak çok iyi olacak. 2'ye ayrı müdahale edip sonra döngüyü tek sayılar üzerinden de ilerletmek biraz hızlandırıyor ama en hızlı yöntem, asal sayı listesi üzerinde gezmek:
[CODE lang="cpp" title="Asal listesiyle asal çarpanlara ayırma"]// Sieve'i en az kök(n)'e kadar yapmanız gerekiyor.
// Mesela Sieve'i 10^8'e kadar yaptıysanız n en fazla 10^16 olmalı.
vector<pair<long long, int>> carpanlara_ayir(long long n, const vector<int>& asallar) {
vector<pair<long long, int>> asal_carpan_kuvvetleri;
for (long long bolen : asallar) {
// Bu da güzel optimize eder, özellikle çok büyük asal çarpan içermeyen sayılar için.
if (bolen * bolen > n)
break;
if (n % bolen)
continue;
int us = 0; // Asal üssü
while (n % bolen == 0) {
n /= bolen;
us++;
}
asal_carpan_kuvvetleri.push_back({bolen, us});
}
// En büyük asal çarpanın üssü 1 ise:
if (n > 1) {
asal_carpan_kuvvetleri.push_back({n, 1});
}
return asal_carpan_kuvvetleri;
}[/CODE]
Aralıktaki asal sayıların sayısını bulmanın 2. yönteminde asal sayıların oranından bahsetmiştim, tekrar bahsedeyim:
n'e kadarki asal sayıların oranı yaklaşık 1/ln(n) civarında. İşte bu sayede operasyon sayısı da bu oranda azalmış oluyor: kök(n) yerine kök(n) / ln(kök(n)).2. Yöntem: n'e kadarki her sayının en küçük asal çarpanını hafızada tutma
Bu yöntem çok hızlı olmasına karşın bir önceki yöntemdeki gibi Sieve'in üst sınırının karesine kadarkiler değil, sadece üst sınıra kadarki sayıları asal çarpanlarına ayırabiliyor; ama bunu çok hızlı yapabiliyor.Şöyle ki: Her sayının en küçük asal çarpanını bildiğinizi düşünün. Bu bilgiyle bir sayıyı sürekli en küçük asal çarpanına böle böle 1'e kadar giderseniz ve bu süreçte böldüğünüz tüm asal çarpanları tutarsanız aslında sayının tüm asal çarpanlarını elde edersiniz. İşin güzel yanı, bir
n tam sayısının en fazla log2(n) adet asal çarpanı olabiliyor. Bunun nedenini anlamak için şunu düşünebilirsiniz:Sayının tüm asal çarpanlarını 2'yle değiştirin. Sonucunda oluşacak sayı
n'den küçük veya n'e eşit olacaktır ve dolayısıyla 2'lerden en fazla log2(n) adet olabilir.Bu yöntemde Sieve mantığından yararlanarak bir liste oluşturmamız gerekecek:
en_kucuk_asal_carpan. (Aslında herhangi bir asal çarpanını tutabilirsiniz; ama en küçüğünü ele alabiliriz Asalların katlarını gezeriz. Kata henüz en küçük asal çarpan tanımlanmamışsa o zaman o kat için en küçük asal çarpan, katlarını gezmekte olduğumuz asaldır.
En küçük asal çarpanları bulma kodunu paylaşayım:
[CODE lang="cpp" title="En küçük asal çarpanları hesaplama fonksiyonu"]vector<int> en_kucuk_asal_carpan_listesi(long long n) {
// asal_mi listesini kullanmamıza gerek yok
vector<int> en_kucuk_asal_carpan(n + 1); // İlk değer 0 olarak alınır, bir şey yazılmazsa.
for (int i = 2; i <= n; i++) {
// En küçük asal çarpanı varsa i asal değildir.
if (en_kucuk_asal_carpan)
continue;
// Bir asalın en küçük asal çarpanı kendisidir.
en_kucuk_asal_carpan = i;
for (int k = 2 * i; k <= n; k += i) {
// Henüz atanmamışsa k'nın en küçük asal çarpanı i'dir.
if (!en_kucuk_asal_carpan[k])
en_kucuk_asal_carpan[k] = i;
}
}
return en_kucuk_asal_carpan;
}[/CODE]
Şimdi de bu listeyi kullanarak
n'e kadarki bir sayıyı asal çarpanlarına ayırma kodunu paylaşayım:[CODE lang="cpp" title="En hızlı asal çarpanlara ayırma kodu"]// Asalın int'i aşacak hali yok, üst sınırdan dolayı. O yüzden long long kullanmadım.
vector<pair<int, int>> carpanlara_ayir(int n, const vector<int> &en_kucuk_asal_carpan) {
vector<pair<int, int>> asal_carpan_kuvvetleri;
// Sayıyı 1 oluncaya kadar sürekli en küçük asal çarpanına bölüyoruz
// ve bu esnada asal carpanları hafızada tutuyoruz.
while (n > 1) {
int asal_carpan = en_kucuk_asal_carpan[n];
int us = 0;
while (n % asal_carpan == 0) {
n /= asal_carpan;
us++;
}
asal_carpan_kuvvetleri.push_back({asal_carpan, us});
}
return asal_carpan_kuvvetleri;
}[/CODE]
Not: 1. yöntemde
n yeterince küçüldüğü zaman 2. yöntemle devam edebilirsiniz. Bunu, bir arkadaşımdan görmüştüm. Yöntemlerin hız karşılaştırması
İki yöntemi de Sieve'in üst sınırına kadarki sayılarla, toplam 4 milyon sorguyla karşılaştırdım (10 milyon sorgu için 15 saniye bile az geldi):- İlk yöntemin testi (3.57 saniye, ~393 MB): Ideone.com
- İkinci yöntemin testi (10.68 saniye, ~50 MB): Ideone.com
Sieve'in kendisinin, direkt kendisi değilse bile mantığının (katları gezme) kullanıldığı daha birçok örnek mevcut; ama ben temel ve temel olduğu kadar önemli 1-2 tanesine değinmek istedim.
Okuduğunuz için teşekkür ederim. Umarım eğlenmişsinizdir.