線性篩法
Eratosthenes 篩法利用的原理是 任意整數 x 的倍數 2x,3x,... 等都不是質數 。
但是即便如此也會有重復標記的現象,例如12既會被2又會被3標記,在標記2的倍數時,\(12 = 6*2\),在標記3的倍數時,\(12 = 4*3\) ,根本原因是沒有找到唯一產生12的方式。
線性篩法的核心原理
每個合數必有一個最大因子(不包括它本身),用這個因子把合數篩掉
換言之:每個合數必有一個最小素因子,用這個因數把合數篩掉
過程
假設對於一個確定的整數 \(i\),\(i\) 是一個合數 \(t\) 的最大因數,\(t\) 顯然可能不唯一(例如 30 和 45 最大因數都是 15)。但是仔細想一想,必然有一個p,滿足:
\[t = i * p ~~ (p\le i ,p是質數) \]
- \(p\)為什么一定小於等於 \(i\)?因為 \(i\) 是 \(t\) 的最大因數。
- 為什么 \(p\) 一定是質數?因為如果 \(p\) 是合數,那么 \(i\) 就一定不是 \(t\) 的最大因數,因為 \(p\) 可以再拆成若干素數相乘,這些素數再與 \(i\) 相乘會使該因數更大。
既然如此,我們只需要把所有小於 \(i\) 的質數 \(p\) 都挨個乘一次拿到所有合數就好了。可是,這樣就不會有重復標記嘛?
會的,我們一不小心就忘記了最初的條件。我們要滿足 \(i\) 是 \(t\) 的最大因數。如果 \(p\) 大於 \(i\) 的最小質因數,那 \(i\) 還是 \(t\) 的最大因數嘛?顯然不是,任何一個合數 \(t\) 都能唯一分解為有限個質數的乘積,除去這其中最小的質因數,其他的都乘起來就是最大因數 \(i\) 。所以我們不能讓 \(p\) 大於 \(i\) 的最小質因數(設為\(x\)),否則 \(i\) 將不再是 \(t = i*p\) 的最大因數,其最大因數應該是\(i*p/x\) 。
下面有兩個版本,核心處理稍有一點點不同,理解即可。
版本一
v[i]
表示 i 的最小質因數。如果i就是質數,那么v[i] = i
prime[j]
表示第 j 個質數。與之前的篩法不同,這個數組是存放質數的,而不是標記質數的
#define MAXN 1000012
int prime[MAXN],v[MAXN];
int m=0;//m表示現在篩出m個質數
void primes(int n)
{
for(int i=2;i<=n;i++)
{
if(v[i]==0)//如果v[i]為0,說明 i 之前沒有被篩到過,i 為質數
{
v[i] = i;
prime[++m] = i;
}
for(int j = 1;j<=m;j++)//遍歷小於 i 的所有質數
{
//如果質數大於 i 的最小質因數或者乘起來大於n就跳出循環
if(prime[j] > v[i] || prime[j] > n/i) break;
v[i*prime[j]] = prime[j];//標記 i*prime[j] 的最小質因數是prime[j]
}
}
}
版本二
v[i]
i 為質數則為0,否則為 1prime[j]
與上面相同
#define MAXN 1000000
int prime[MAXN],v[MAXN];
int m=0;//m表示現在篩出m個質數
void primes(int n)
{
v[1] = 1;//1不是質數,提前處理
for(int i=2;i<=n;i++)
{
if(v[i]==0)//如果v[i]為0,說明 i 之前沒有被篩到過,i 為質數
prime[++m] = i;
for(int j = 1;j<=m;j++)//遍歷小於 i 的所有質數
{
//乘起來大於就跳出循環
if(prime[j] > n/i) break;
v[i*prime[j]] = 1;//標記 i*prime[j] 的最小質因數是prime[j]
//當遇到最小的質數是i的因數時,break
if(i%prime[j]==0)break;
}
}
}