质数(prime number)又称素数,有无限个。质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数,这样的数称为质数。
对于判定质数,有一个很显然的方法就是判断除了1和他本身之外有没有其他的因数了。
1 bool ok(int N) 2 { 3 if(N==1) return 0; 4 for(int i=2;i<N;++i) 5 if(!N%i) return 0; 6 return 1; 7 }
但是有一个简单粗暴的优化方案是判断到sqrt(N)之内的因子就好了,因为因子是成对出现的,假如a是一个因子,那么N/a也是他的因子,显然当a=sqrt(N)时二者相等,如果再继续找到的因子a那么N/a一定出现在sqrt(N)之前,所以我们只判定到sqrt(N)即可。
bool ok(int N) { if(N==1) return 0; int m=sqrt(n+0.5); for(int i=2;i<=m;++i) if(!N%i) return 0; return 1; }
但是对于大量素数的判定,如果还用这个判定法的话也会很耗时,这是筛法就派上用场了。筛法是一种简单检定素数的算法。据说是古希腊的埃拉托斯特尼(Eratosthenes,约公元前274~194年)发明的,又称埃拉托斯特尼筛法(sieve of Eratosthenes)。
筛法的思想也很简单,枚举所有的素数筛去他们所有的倍数的数(在求解范围之内),显然剩下的数都是素数。但其实,我们不必枚举所有的素数,枚举到sqrt(N)之内的素数即可,这样的正确性在于: 当我们枚举到一个素数x的时候,他的倍数有2,3......x-1,这些倍数如果是质数的话,因为小于x所以之前已经被筛过了,如果是合数根据算术分解定理:算术基本定理可表述为:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积 N=P1a1P2a2P3a3......Pnan,这里P1<P2<P3......<Pn均为质数,其中指数ai是正整数。所以也能分解出更小的质数,所以在之前也已经筛过。所以直接从x的x倍开始筛,
这样的缺点在于不能讲将所有的素数打表,但是能筛出一个判别数组,判定isp[i]是否为真。
1 bool isp[MAXN]; 2 int prime[MAXN], p = 0; 3 void init(int N) 4 { 5 isp[0] = isp[1] = 1; 6 for (int i = 2;;++i) { 7 if (!isp[i]) { 8 //prime[p++] = i; 9 for (int j = i*i;j <= N;++j) 10 isp[j] = 1; 11 } 12 if (i*i > N) break; 13 } 14 }
接下来说的就是一种线性筛法,欧拉筛法,不仅复杂度更低,还能将判别表和素数表都生成出来。
先上代码:
1 bool isp[MAXN]; 2 int prime[MAXN], p = 0; 3 void init(int N) 4 { 5 isp[0] = isp[1] = 1; 6 for (int i = 2;i <= N;++i) 7 { 8 if (!isp[i]) prime[++p] = i; 9 for (int j = 1;j <= p&&i*prime[j] <= N;++j) { 10 isp[i*prime[j]] = 1; 11 if (i%prime[j] == 0) break; 12 } 13 } 14 }
首先这个方法真实运行起来不一定比埃式要快,因为有取模操作,但是他可以求解一些积性函数问题 ,这个以后再说。之所以说他是线性的是因为对于每个合数只会被他最小的质因子筛去一次。实现这一特性的就在于那句break语句。首先我们知道prime[]数组里的素数是递增的,如果i%prime[j]==0,则我们可以认为i=M*prime[j],
后面break掉的就是i*prime[k],他可以表示为M*prime[j]*prime[k],也就是说在后面他会被prime[j]筛掉,所以可以直接break。对于每个筛去的数i*prime[j],prime[j]都是这个数的最小质因子。