1.埃氏篩
思想:任意合數都可以表示成幾個素數的乘積,那麼我們可以每找到一個素數,就將他的倍數都標記(代表這個數是合數)。2是最小的素數,所以我們從2開始標記。時間複雜度爲:O(n*logn)
代碼如下:
const int maxn=1e5+10;
bool flag[maxn];
int prime[maxn];
int pri_cnt=0;
void get_prime(int n){
for(int i=2;i<=n;i++){
if(!flag[i]){
prime[++pri_cnt]=i;
for(int j=i*i;j<=n;j+=i){
flag[j]=1;
}
}
}
}
對於上面j=i*i
可能會有人有疑問,按照上面的思路,初始值j
不應該等於2*i
嗎,因爲區間i*(2~ i-1)
在 2~i-1
時都已經被篩去,所以從i * i
開始.
2.歐拉篩
思想:由埃氏篩我們已經知道篩法的核心就是標記,但是我們可以發現:即便我們做了一點優化,隨着數據量的增長,一些擁有多個素因子的合數會被重複標記。我們可以想到用唯一素數來標記合數,這樣每個合數都只會被標記一次,因此時間複雜度降到O(n);
代碼如下:
const int maxn=1e5+10;
int v[maxn],prime[maxn];//數組v記錄每個數的最小質因子
int pri_cnt=0;//pri_cnt記錄質數的個數
void prime_table(int n){
for(int i=2;i<=n;i++){
if(v[i]==0){//i是質數
v[i]=i;
prime[++pri_cnt]=i;
}
for(int j=1;j<=pri_cnt;j++){
//如果(i*prime[j])有比prime[j]更小的因子,或者超出n的範圍,停止循環
if(prime[j]>v[i] || prime[j]>(n/i)) break;
//prime[j]是合數i*prime[j]的最小因子
v[i*prime[j]]=prime[j];
}
}
}
prime[j]>v[i]
表示當前的素數prime[j]
比累乘因子i
的最小素因子大,這就代表合數i*prime[j]
應該被i
的最小素因子v[i]
篩掉,而不是被當前的素數prime[j]
篩掉,舉個例子:12=4x3;當i=4 prime[j]=3
時,12本應該被2篩掉,也就是被4的最小素因子2(v[4]=2
)篩掉,輪不到3來篩,所以跳出循環。
prime[j]>(n/i)
是prime[j]*i>n
的意思,用除法不用乘法是爲了防止溢出(當數據量很大的時候,會爆int)