判断一个数n是否是素数,众所周知可以用O(sqrt(n))的方法。
但是如果要求很多个数,这个方法就不太好了。(比如所有小于n的数,复杂度就是O(n1.5)。)
埃拉托斯特尼筛法,大家都听说过。从2到n,去掉每个数的倍数,剩下来的就是质数。
不过这个方法会重复删除,比如6是2、3的倍数,会被删2次,因子越多,删的次数就越多。
改进之后的线性筛保证每个数只被最小的质因子删,所以是O(n)的。
#include<cstdio>
#include<cstring>
#define MAXN 100005
#define MAXL 1299710
int prime[MAXN];
int check[MAXL];
int tot = 0;
memset(check, 0, sizeof(check));
for (int i = 2; i < MAXL; ++i)
{
if (!check[i])
{
prime[tot++] = i;
}
for (int j = 0; j < tot; ++j)
{
if (i * prime[j] > MAXL)
{
break;
}
check[i*prime[j]] = 1;
if (i % prime[j] == 0)
{
break;
}
}
}
tot是计数用的,prime保存质数,check是判断是否是质数。
1.任意一个合数 A = p1p2...pn,(其中p1<=p2<=...<=pn) ,i=p2p3...pn时会删掉。
2.A只会被p1删掉。若i是prime[j]的倍数,A不会被p[j+1]删掉,当i=A/p[j+1]时,i%p[j+1]==0,break。如果不退出,A就被p[j+1]删了。
可以看出,这个方法需要额外的prime数组。而埃氏筛不必要。
顺便可以求欧拉函数
#include<cstdio>
#include<cstring>
#define MAXN 100005
#define MAXL 1299710
int prime[MAXN];
int check[MAXL];
int phi[MAXL];
int tot = 0;
phi[1] = 1;
memset(check, 0, sizeof(check));
for (int i = 2; i < MAXL; ++i)
{
if (!check[i])
{
prime[tot++] = i;
phi[i] = i - 1;
}
for (int j = 0; j < tot; ++j)
{
if (i * prime[j] > MAXL)
{
break;
}
check[i*prime[j]] = 1;
if (i % prime[j] == 0)
{
phi[i*prime[j]] = phi[i] * prime[j];
break;
}else
{
phi[i*prime[j]] = phi[i] * (prime[j]-1);
}
}
}
n为质数,phi(n)=n-1
n为合数,进行质因数分解。$\large n = p_1^{k_1}\times p_2^{k_2}\times ... \times p_n^{k_n}$
$$\large \varphi(n) = n \prod\limits_{i = 1} ^ {n} \frac{p_i - 1}{p_i}$$
筛掉n=i*prime[j]时,求$\varphi(n)$,筛和求是同步的,也是O(n)。
设p1是n的最小质因子,n'=n/p1。
若n'%p1=0,即k1>1,n'含有n的所有质因子,则有
$$\large \begin{align} \varphi(n)&= n \prod\limits_{i = 1} ^ {n} \frac{p_i - 1}{p_i} \\ &= p_1 \times n' \prod\limits_{i = 1} ^ {n} \frac{p_i - 1}{p_i} \\ &= p_1 \times \varphi(n') \\ \end{align}$$
若n'%p1≠0,即k1=1,n'与p1互质,有
$$\large \begin{align} \varphi(n)&= n \prod_{i = 1} ^ {n} \frac{p_i - 1}{p_i} \\ &= p_1 \times n' \times \frac{p_1-1}{p_1} \prod\limits_{i = 2} ^ {n} \frac{p_i - 1}{p_i} \\ &= (p_1-1) \times \varphi(n') \\ \end{align}$$
(这也证明了欧拉函数是积性函数)
ref:
代码全是抄这里的。
欧拉函数的公式全是抄这里的。
延伸:
只筛奇数。
我推荐这个算法! 易于理解。 只算奇数部分,时空效率都还不错!
half=SIZE/2;
int sn = (int) sqrt(SIZE);
for (i = 0; i < half; i++)
p[i] = true;// 初始化全部奇数为素数。p[0]对应3,即p[i]对应2*i+3
for (i = 0; i < sn; i++) {
if(p[i])//如果 i+i+3 是素数
{ //每次j+=k,2*(j+k)+3=(2*j+3)+2k,也就是在上一个筛的数上+2k
for(k=i+i+3, j=k*i+k+i; j < half; j+=k)
// 筛法起点是 p[i]所对应素数的平方 k^2
// k^2在 p 中的位置是 k*i+k+i
// 下标 i k*i+k+i
//对应数值 k=i+i+3 k^2
p[j]=false;
}
}
//素数都存放在 p 数组中,p[i]=true代表 i+i+2 是素数。
//举例,3是素数,按3*3,3*5,3*7...的次序筛选,因为只保存奇数,所以不用删3*4,3*6....
来自:https://blog.csdn.net/dinosoft/article/details/5829550
来源:oschina
链接:https://my.oschina.net/u/4351710/blog/3959249