预备知识:
首先不管是什么方法的筛质数,其最基本的思想都是一样的:假设P是任意一个数,如果这个数没有被2~P-1之间的任何数筛掉,那么它就是质数
算术基本定理可表述为:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积(取自百度)
int p[N], cnt; //p[N]用来存质数,cnt表示质数的个数
bool st[N]; //表示当前的数有没有被筛过
朴素筛法
void get_primes(int n)
{
for(int i = 2; i <= n; i ++)
//对于每一个i,从i~n中找到i的每一个倍数并筛掉
if(!st[i]) p[cnt ++] = i; // 如果没有被筛掉,那么就是质数
for(int j = i + i; j <= n; j ++)
st[j] = true;
}
由于当运行到 i 时可以确保已经用小于 i 的每一个质数都筛过一遍,因此可以保证每一个合数都会被筛掉
埃式筛法
在朴素筛法中,2 * 4 与 4 * 2 的结果都是8,因此一个数可以用合数筛的时候,其实已经被质数已经筛过一次,所以可以只在 i 是素数的时候筛
void get_primes(int n)
{
for(int i = 2; i <= n; i ++)
if(!st[i])
{
p[cnt ++] = i;
for(int j = i + i; j <= n; j ++)
st[j] = true;
}
}
由算术基本定理知一定可以将每一个合数都筛掉(因为每一个合数都能分成若干个质数)
线性筛法 , n = 1e7时基本上就比埃式筛法快一倍
算法核心:保证每个合数只会被其最小质因子筛掉
void get_primes(int n)
{
for(int i = 2; i <= n; i ++)
if(!st[i]) p[cnt ++] = i;
for(int j = 0; p[j] * i <= n; j ++)
{
st[p[j] * i] = true;
if(i % p[j] == 0) break;
}
}
首先证明合数是不是一定会被筛掉
假设N是一个合数,且P是N的最小质因子
只需要满足P小于
,当 i 等于
时,N就会被筛掉
反证法,假设
小于P
如果
是质数,那么
就是N的最小质因子,故
小于P不成立
如果
是合数,那么由算术基本定理,
一定可以分出一个比P小的素数来作为N的最小质因子,故
小于P不成立
综上,P一定小于
,故每一个合数肯定会被筛掉
其次证明是不是每个合数只会被其最小质因子筛掉
for(int j = 0; p[j] * i <= n; j ++)
{
st[p[j] * i] = true;
if(i % p[j] == 0) break;
}
如果 i % p[ j ] == 0 说明p[ j ]是 i 的最小质因子(因为p[ j ]是从小到大枚举的), 所以p[ j ]也是 i * p[ j ]的最小质因子
如果 i % p[ j ] != 0 说明p[ j ]小于 i 的所有质因子(因为p[ j ]是从小到大枚举的),所以p[ j ]也是 i * p[ j ]的最小质因子
而至于为什么要break,因为一旦 i % p[ j ] 为0时,说明p[ j ]是 i 的最小质因子,而此时如果继续循环下去,用p[ j + 1 ] * i 筛时,由于 i 的最小质因子p[ j ]小于p[ j + 1],因此这时候被筛掉的合数就不是被他的最小质因子筛掉的了
所以break也是保证线性筛法时间复杂度的一个重要原因~