徹底理解線性篩選法

2022-05-30 16:00:17 字數 2886 閱讀 2115

問題:求所有小於等於n(n比較大)的所有素數

首先可能最容易想到的是寫乙個函式來判斷它是不是素數,但是對於求比n小的整數就顯得時間複雜度太高了,一般解這種問題會採用篩選法...

思想是,使用乙個位陣列is_prime儲存每乙個數是否是素數,然後每次找到乙個素數x,就把這個素數的i倍,i滿足(x * i <= n)踢掉,即將is_prime設定為true(預設是false)。這樣找到乙個數後,再找的下乙個is_prime = false的數,一定是素數(因為如果不是,它就會被某個比它小的數因子踢出掉)

埃氏篩選法的效率已經不低了,但是會有很多的重複計算,即乙個數會被多次踢出,浪費了計算時間。對於任何乙個合數z,一定存在質數x,使得:

z = x * y (y 不為 1)

但是這樣的x不唯一,比如6=23=32,這裡x = 2或者3。所以我們現在要找乙個辦法,讓每乙個合數只能被踢出一次而不是多次。這時注意到x的最小值是一定的,所以我們可以用每乙個合數的最小質因子來踢出。

即:

10由2踢出

9由3踢出

77由7踢出

這樣就實現了乙個合數被乙個最小質數踢出,大大加快了運算效率。

那麼在程式實現時怎麼做呢?

我們可以在遍歷2-n時,用當前已經找到的每乙個質數去乘以這個當前數i,在這些質數眼裡(不是當前遍歷的數!!),這樣就相當於:

質數2分別與2,3,4,5,6,7,8,9...,踢出了(4,6,8,10,12,14,16,18)

質數3分別與3,4,5,6,7,8,9...踢出了(9,12....)

質數5分別與5,6,7,8,9,10,11,12...,踢出了(25,30...)

質數7分別與3,4,5,6,7,8,9...踢出了(21,28....)

這樣看起來還是有不少重複啊,不要急,目前這種方式相當於埃氏篩選法,這裡最核心的一步是,如果當前數i的因子已經有了某個質數的話,那麼當前數i就不再繼續與下乙個質數相乘了。

什麼意思?

就是說,當i = 6時(遍歷到6這個合數時),我先把2 * 6 = 12踢掉,當我想繼續再想把3(第二個質數) * 6 = 18頁踢掉時,這時候等一等!由於:

6 % 2 == 0

這個時候就不要繼續往下走了,因為將要計算的下乙個要踢出的數的質最小因子一定是剛剛那個質數(2)!,為什麼?因為當前數都可以整除剛剛那個質數了!

這樣就保證了乙個合數只能被一次踢出,比如:

100 一定是在i = 50被踢掉的

70 一定是在i = 35被踢掉的

45 一定是在i = 15被踢掉的

證明:

首先證明合數t一定會被踢掉:

對於t,一定存在最小質數a,所以當遍歷到i = t / a 時,踢掉t/a * a = t

然後證明t只會被踢一次

使用反證法,設i = x,i = y時踢出t,這裡x < y,a、b是質數且

a是最小質因子(已證明這個一定存在),則

t = x * a

t = y * b

由於任意乙個合數都可以表示為am*bn.....其中a,b,c...是質數,如45=3^2*5

所以當b!=a時,b一定大於a,這個時候迴圈已經超過a了(a是最小質因子),而45已經含有a這個因子,所以有t % a == 0得,此時迴圈已經break,因此不會重複計算任意合數。

#include #include using namespace std;

vectorprimes;

bool *is_prime;

void get_all_prime(int n)

} }}int main()

cout << primes.size();

return 0;

}

計算[2,1000000000]素數花了26s

不使用vector花了24s

#include #include using namespace std;

vectorprimes;

bool *is_prime;

void get_all_prime(int n)

int size = primes.size();

for(int j = 0; j < size && primes[j] * i <= n; j++) }}

int main()

cout << primes.size();

return 0;

}

計算[2,1000000000]素數花了19s

#include #include #define max_cap 100000000

using namespace std;

bool *is_prime;

int data[max_cap];

int size = 0;

void get_all_prime(int n)

if(n == 3)

is_prime = new bool[n + 1];

for(int i = 2; i <= n; i++)

for(int j = 0; j < size && data[j] * i <= n; j++) }}

int main()

計算[2,1000000000]素數花了9s。。。

線性篩選素數法

pri 1到pri n遞增,composite num1 n 在遍歷 1,n 的數時,剛好maxdivisor1 pri 3 pri 4 pri n 1 pri n。pri 1是是它最小的質因數。maxdivisor如果乘以乙個大於pri 3的質因數,例如pri 4,就找到另乙個合數 composi...

素數與線性篩選法初級版

今天我們來學習一些關於素數和線性篩選法的知識,這類問題在acm icpc中常常遇到,所以很有必要學好它。首先,來看素數篩選的乙個題。題目 題意 給定區間 分析 由費馬平方和定理知道,乙個奇素數 只需要先篩選出所有素數,然後乙個乙個判斷即可,但是這個區間可能很大。普通的素數篩選法是用bool數 組,佔...

素數篩選法(線性篩法模板)

include using namespace std const int n 1e7 bool flag n 10 int prime n 10 int main return0 include using namespace std typedef long long ll int prime ...