回忆一下经典的埃式筛法求素数。时间复杂度是为 O(nloglogn)(我之前一直以为是O(n)) O ( n l o g l o g n ) ( 我 之 前 一 直 以 为 是 O ( n ) )
int ans[MAXN];
void Prime(int n)
{
int cnt=0;
memset(prime,1,sizeof(prime));
prime[0]=prime[1]=0;
for(int i=2;iif(vis[i])
{
ans[cnt++]=i;//保存素数
for(int j=i+i;j//i*i开始进行了稍微的优化
prime[j]=0;//不是素数
}
}
return ;
}
显然,当一个数是素数的时候,那么他的倍数肯定是合数,筛选标记即可
我们来举个列子
筛选2-10的素数
首先2是素数,然后把其倍数删去,我们标记下删去的次数用一个斜杠表示删去了一次
之后3是素数,继续删去倍数,这个时候发现6被重复删去两次
之后5是素数,删去倍数,发现10被重复删去两次
正是因为重复删去的问题导致它的复杂度并不是O(n)
因此引出了下面的方法,欧拉筛法,时间复杂度 O(n) O ( n )
code:
/*求小于等于n的素数的个数*/
#include
#include
using namespace std;
int main()
{
int n, cnt = 0;
int prime[100001];//存素数
bool vis[100001];//保证不做素数的倍数
scanf("%d", &n);
memset(vis, false, sizeof(vis));//初始化
memset(prime, 0, sizeof(prime));
for(int i = 2; i <= n; i++)
{
if(!vis[i])//不是目前找到的素数的倍数
prime[cnt++] = i;//找到素数~
for(int j = 0; jtrue;//找到的素数的倍数不访问
if(i % prime[j] == 0) break;//关键!!!!
}
}
printf("%d\n", cnt);
return 0;
}
根据代码我们模拟一下
首先2是素数,存下来,然后删去已有素数的当时数倍,即现在是删去已有素数的2倍
然后下一个3是素数,存下来,删去已有素数的3倍,当删去 3×3 3 × 3 之后发现其实3可以整除3就停止break,当然了后面本来也没素数了本来也应该停止,因此我们先不管,继续往后看
这个时候到4,虽然4不是素数,但是我们仍然要进行删除操作呀
这个时候是首先会把 4×2=8 4 × 2 = 8 删掉,然后判断发现 2|4 2 | 4 因此终止
后面本来该删去12但是为什么不删除呢
原来我们发现继续往后到达6的时候,6乘2会删掉12,而如果我们在4的时候删除了,后面又会重复删除,和第一种方法就没区别了。
实际上我们是定了一个标准,因为每个合数必有一个最小素因子,所以我们让每个合数仅被它的最小素因子筛去正好一次,也就是12让2这个素因子筛去,而不是3
对于一般情况代码if(i % prime[j] == 0) break;
因为prime数组中的素数是递增的,当i能被prime[j]整除的时候,看也看成 i=prime[j]×k i = p r i m e [ j ] × k
那么对于后面的 i×prime[j+1]=prime[j]×k×prime[j+1]=prime[j]×kk i × p r i m e [ j + 1 ] = p r i m e [ j ] × k × p r i m e [ j + 1 ] = p r i m e [ j ] × k k
所以 i×prime[j+1] i × p r i m e [ j + 1 ] 这个数不应该现在删除,而是等后面一定会出现一个kk是的它可以被最小素因子prime[j]删除,因此保证线性的复杂度。
那么既然讲到了素数的筛选我们必然会想到欧拉函数的筛选,因为我们之前会的欧拉函数的筛选是基于埃式筛素数的方法之上的
下面仍然是先给出朴素的欧拉函数筛选(基于埃式筛法)时间复杂度 O(nloglogn)(我之前同样以为是O(n) O ( n l o g l o g n ) ( 我 之 前 同 样 以 为 是 O ( n ) - _ - ||)
code:
//筛选法打欧拉函数表
#define Max 1000001
int euler[Max];
void Init(){
euler[1]=1;
for(int i=2;ifor(int i=2;iif(euler[i]==i)
for(int j=i;j1);//先进行除法是为了防止中间数据的溢出
}
对于欧拉函数的一些性质和上面这个模板公式的推导由来可以看一下欧拉函数模板
下面我们直接使用里面的几条性质进行新公式的推导
结论:
设 P P 是素数,
若p是x的约数,则ϕ(x×p)=ϕ(x)×p 若 p 是 x 的 约 数 , 则 ϕ ( x × p ) = ϕ ( x ) × p .
若p不是x的约数,则ϕ(x×p)=ϕ(x)×ϕ(p)=ϕ(x)×(p−1) 若 p 不 是 x 的 约 数 , 则 ϕ ( x × p ) = ϕ ( x ) × ϕ ( p ) = ϕ ( x ) × ( p − 1 ) .
对于第二个结论直接是利用其积性函数的性质我们不再证明
对于第一条结论我们下面简单证明
我们知道
(我们只说明第一条结论故规定 k≥2 k ≥ 2 )
所以
而
观察两个式子我们可以发现欧拉函数 ϕ(pk)和ϕ(pk−1)之间之差了一个因子p ϕ ( p k ) 和 ϕ ( p k − 1 ) 之 间 之 差 了 一 个 因 子 p
所以
因此结论1得证
那么根据上面已经讲到的线性筛选素数的方法,我们发现恰好适用于筛选欧拉函数
于是有了下面的代码:
#include
#include
#define N 40000
using namespace std;
int n;
int phi[N+10],prime[N+10],tot,ans;
bool mark[N+10];
void getphi()
{
int i,j;
phi[1]=1;
for(i=2;i<=N;i++)//相当于分解质因式的逆过程
{
if(!mark[i])
{
prime[++tot]=i;//筛素数的时候首先会判断i是否是素数。
phi[i]=i-1;//当 i 是素数时 phi[i]=i-1
}
for(j=1;j<=tot;j++)
{
if(i*prime[j]>N) break;
mark[i*prime[j]]=1;//确定i*prime[j]不是素数
if(i%prime[j]==0)//接着我们会看prime[j]是否是i的约数
{
phi[i*prime[j]]=phi[i]*prime[j];break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);//其实这里prime[j]-1就是phi[prime[j]],利用了欧拉函数的积性
}
}
}
int main()
{
getphi();
}
而这有让我想起了,莫比乌斯函数的筛选,他们都基于线性筛素数的算法
void Init(){
int N=maxn;
memset(prime,0,sizeof(prime));
memset(mu,0,sizeof(mu));
memset(vis,0,sizeof(vis));
mu[1] = 1;
cnt = 0;
for(int i=2; iif(!vis[i]){
prime[cnt++] = i;
mu[i] = -1;
}
for(int j=0; j1;
if(i%prime[j]) mu[i*prime[j]] = -mu[i];
else{
mu[i*prime[j]] = 0;
break;
}
}
}
}