線性篩(歐拉篩)
使用範圍:得到[2…N]之間所以素數
說實話埃氏篩已經足夠優秀,能基本做到o(n),但歐式篩纔是真正的線性篩且能在篩出質數的同時運算歐拉函數和莫比烏斯函數這兩個積性函數的運算,實用於處理數論問題。
歐拉篩拆解分析
(一)初始化
int n,pn=0;//
int num[maxl], prim[maxl],phi[maxl],mob[maxl];
其中
- num[x]值域爲[-1,0],表示數x是否爲素數(-1)
- prim[x]記錄篩出的第x+1項素數
- phi[x],mob[x]分別記錄歐拉函數和莫比烏斯函數的值
(二)第一重循環
for (int i = 2; i < n; ++i)
i有雙重含義,既表示當前指向的應該拿去判斷是否爲素數的數有點拗口,又表示篩數的乘子(後文會解釋)
(三)判別素數
if (num[i]) {
prim[pn++] = i;
phi[i] = i - 1;
mob[i] = -1;
}
只要num[i]是-1就代表沒被篩除是素數,這裏同時寫了歐拉函數和莫比烏斯函數的值。
(四)第二重循環
for (int j = 0; j < pn && i * prim[j] < n; ++j)
將之前篩出的素數的i倍篩除美滋滋
(五)具體篩除操作,很關鍵
if (i % prim[j] == 0) {
num[i * prim[j]] = 0;
//mob[i * prim[j]] = 0;//莫比烏斯函數
//phi[i * prim[j]] = phi[i] * prim[j];//歐拉函數
break;
}
num[i * prim[j]] = 0;
//mob[i * prim[j]] = -mob[i];
//phi[i * prim[j]] = phi[i] * (prim[j] - 1);
爲什麼要i%prim[j]==0就break呢,因爲如果i已經包含了prim[j]這個素因子,那麼用篩去i*prim[k] (k>j) 就沒必要了,因爲可以用i增大後乘以prim[j] 來代替,這步操作真正做到了線性,即每個數只篩一次。
到這裏線性篩就講解完了,下面附上運用線性篩和歐拉函數性質的[SDOI2008]儀仗隊題解
#include<iostream>
#include<string.h>
#define maxl 40000
using namespace std;
void Prime(int*num,int*prim,int*phi,int*mob,int n,int&pn) {
pn = 0;
memset(num,-1,sizeof(num));
for (int i = 2; i < n; ++i) {
if (num[i]) {
prim[pn++] = i;
phi[i] = i - 1;
mob[i] = -1;
}
for (int j = 0; j < pn && i * prim[j] < n; ++j) {
if (i % prim[j] == 0) {
num[i * prim[j]] = 0;
mob[i * prim[j]] = 0;
phi[i * prim[j]] = phi[i] * prim[j];
break;
}
num[i * prim[j]] = 0;
mob[i * prim[j]] = -mob[i];
phi[i * prim[j]] = phi[i] * (prim[j] - 1);
}
}
}
int main() {
int n,pn,result = 0;
int num[maxl], prim[maxl],phi[maxl],mob[maxl];
cin >> n;
Prime(num, prim, phi, mob, n, pn);
for (int i = 2; i < n; ++i)
result = phi[i] + result;
cout << (result * 2 + 3);
}