质数:一个大于1的自然数,除了1和他本身没有其他因数的数叫做质数(素数),否则称为合数。
如何判断一个数是否为质数:
- 从2枚举到n-1,判断是否存在n的因子,复杂度O(n)
- 从2枚举到 n \ \sqrt[]{n} n ,复杂度O(n),因为n的因子总是成对出现的,若m是它的因子,那么n/m也是它的因子,所以不用再遍历 n \ \sqrt[]{n} n ~ n n n的数。
质数的筛法:
1. 逐个判断,O( n n n\ \sqrt[]{n} n n)
2. 利用质数的性质,O( n n 3 \ {n\ \sqrt[]{n}\over 3} 3n n )
性质:每个(大于等于5的)质数都可以表示为6 x x x ± \pm ± 1,所以遍历是我们只用判断6 x x x+1和6 x x x-1是否为质数
3. 埃氏筛法,O( n l o g l o g n nloglogn nloglogn)
- 基本思想 :唯一分解定理,比如:168=2 * 2 * 2 * 3 * 7=23 * 3 * 7,所以每个合数都可以表示成多个质数的乘积,也可以说它是某个质数的倍数。
- 算法实现 :从2开始,将每个质数的倍数都标记成合数,以达到筛选素数的目的。
代码:
bool vis[manx];
void make_prime(int n)
{
memset(vis,0,sizeof(vis));//0表示素数,1表示合数
vis[0]=vis[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
for(int j=i+i;j<=n;j+=i)
/*这里可以优化成for(int j=i*i;j<=n;j+=i),但注意i*i会不会爆int*/
vis[j]=1;
}
}
}
4. 线性筛(欧拉筛法),O( n n n)
- 基本思想:上面埃氏筛法说道,从2开始,将每个质数的倍数都标记成合数,这样就会出现一个问题——同一个数会被重复筛选,比如168被2筛选了之后,还会被3、7再筛选一次,现在我们让每个合数只被它的最小公约数筛选一次,这就是欧拉筛选。
代码:
int prime[manx];
bool vis[manx];
void make_prime(int n)
{
int cou=0;//素数的个数
memset(prime,0,sizeof(prime));
memset(vis,0,sizeof(vis));
vis[0]=vis[1]=1;//0和1不是素数
for(int i=2;i<=n;i++)
{
if(!vis[i])
prime[cou++]=i;//记录素数
for(int j=0;j<cou;j++)
{
if(i*prime[j]>n)break;
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
-
解释:虽然欧拉筛和第三种埃氏筛思路相近,都是用质数去筛合数,都有两层循环,但它们代码实现的思路却不一样。埃氏筛是直接遍历数组vis(即1~n的数),遇到质数就把这个质数的倍数(1 ~n范围内)全都标记一遍。
-
而欧拉筛是把第一层循环的i作为质数的倍数,一步一步地筛去数组prime中存的质数的1倍、2倍、、、n倍,优化的关键点就在于if(i%prime[j]==0)break;因为prime是一个有序的数组,存放了从小到大的质数,当i%prime[j]==0时,i=prime[j] *k(其中k≥prime[j],因为prime[j]是i的最小因子),那么i *prime[j+x]就可以表示成prime[j]*prime[j+x]*k,所以i *prime[j+x]会在以后i=prime[j+x]*k时,被prime[j]筛掉。(也就是说,当i%prime[j]==0时,因为i *prime[j+x]有更小的因子prime[j]所以它不应该在此时被素数prime[j+x]筛去)
-
借用博客中输出的打表的方法:
可以看到运行到i=6时,并没有将6 * 3和6 * 5筛掉,而18和30这两个数分别在i=9和i=15时被2筛掉了
#include <iostream>
#include <iostream>
#include <string.h>
#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
const int manx=1e7+10;
const int INF=1e9;
int n,m;
int prime[manx];
bool vis[manx];
void make_prime(int n)
{
int cou=0;
memset(prime,0,sizeof(prime));
memset(vis,0,sizeof(vis));
vis[0]=vis[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])
prime[cou++]=i;
for(int j=0;j<cou;j++)
{
if(i*prime[j]>n)break;
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int main()
{
int num;
make_prime(manx);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d",&num);
/*if(num==1)
{
printf("Yes\n");
continue;
}*/
if(vis[num])
printf("No\n");
else
printf("Yes\n");
}
}
用埃筛和欧拉筛进行欧拉函数打表:求欧拉函数值 && 打表O(n)