1. 程式人生 > >【模板】線性篩素數(埃篩+歐篩)

【模板】線性篩素數(埃篩+歐篩)

本來打算自己寫一篇的,但在找埃篩的程式碼時找到了一篇不錯的題解,修改了一點內容上的表述分享出來,原作者的洛谷ID為 dormantbs

我們常說的線篩是指線上性時間內把素數篩出來的過程,這裡介紹兩種篩法.

一般篩法(埃拉託斯特尼篩法,之後簡稱為埃篩):

基本思想:素數的倍數一定不是素數

實現方法:

用一個長度為N+1的陣列儲存資訊(0表示素數,1表示非素數)
先假設所有的數都是素數(初始化為0)
從第一個素數2開始,把2的倍數都標記為非素數(置為1),一直到大於N;
然後進行下一趟,找到2後面的下一個素數3,進行同樣的處理
直到最後,陣列中依然為0的數即為素數。
說明:整數1特殊處理即可。

舉個例子

我們篩前20個數

首先初始為(0代表不是素數,1代表是素數)

0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

然後從2開始我們發現2被標記為素數,我們把2的倍數全部篩掉

變為:

0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

接著到3我們發現3仍然被標記,把3的倍數全部篩掉

變為:

0 1 1 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0

接著一直重複下去就得到了最後的素數表:

0 1 1 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0

2 3 5 7 11 13 17 19

const int MAXN = 1000000void
shai() { int i,j; for(i=0;i<MAXN;i++) prime[i]=1; prime[0]=prime[1]=0;//特殊處理 for(i=2;i<MAXN;i++) { if(!prime[i]) continue; for(j=i*2;j<MAXN;j+=i) prime[j]=0; } }

調和級數證明可得時間複雜度為(nlglgn),所以不能稱之為線性運算,雖然它的實際執行速度也不是特別慢,但有些大資料會過不去(可以去洛谷線篩模板題

嘗試一下,如果我埃篩沒寫錯的話會 TLE 7個點)

下面我們來介紹一波真正的線性篩法(尤拉篩法):

我們發現在上面的篩法中有的數字是多個素數的倍數,也就是說它可能會被重複計算多次,比如說6同時是2與3的倍數,它在計算時就被訪問了兩次,這樣會導致效率低下,所以在下面的演算法中我們考慮如何優化這種情況。

原理:

任何一個合數都可以表示成一個質數和一個數的乘積

假設A是一個合數,且A = x * y,這裡x也是一個合數,那麼有:

A = x * y; (假設y是質數,x合數)

x = a * b; (假設a是質數,且a < x——>>a < y)

-> A = a b y = a Z (Z = b y)

即一個合數(x)與一個質數(y)的乘積可以表示成一個更大的合數(Z)與一個更小的質數(a)的乘積,那樣我們到每一個數,都處理一次,這樣處理的次數是很少的,因此可以線上性時間內得到解。

仍然按上面的例子模擬(這裡0為是素數,1為非素數,p為記錄的素數表):

初始:

1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

p(empty)

然後到2的位置,把2放入素數表,做當前範圍內可以篩掉的處理(具體是怎樣的看程式碼):

1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

p 2 到3,把3放入素數表,繼續處理

1 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0

p 2 3 然後到了4,它不是個素數,也處理一下

1 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0

p 2 3 …….

然後一直重複操作,最後也能得到完整的素數表,這樣雖然看起來複雜一些,但是實際上我們發現對於每個數的處理幾乎是O(1)的。

 void get_list(){
       for(int i=2;i<=maxn;i++){
             if(!is_not_pr[i]) prime[++tot]=i;
             for(int j=1;j<=tot&&i*prime[j]<=maxn;j++){
                   is_not_pr[i*prime[j]]=1;
                   //合數標為1,同時,prime[j]是合數i*prime[j]的最小素因子
                   if(i%prime[j]==0) break;
                    //即比一個合數大的質數和該合數的乘積可用一個更大的合數和比其小的質數相乘得到
             }
       }
}