1.筛法简介
筛选法又称筛法,具体做法是:先把N个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一直做下去,就会把不超过N的全部合数都筛掉,留下的就是不超过N的全部质数。
//首先先说一下最朴素的判断素数的方法
int prime(int n)
{
for(int i=2;i<sqrt(n);i++)
{
if(i%n==0)
return 0;//不是素数
}
return 1;//是素数
}
可以看出最多需要遍历 次才能判断出来n是素数时间复杂度相对来说比较高,但是我们看接下来这种算法,时间会减少许多
//分为两大类//
· 埃氏筛法(埃拉托斯特尼筛法)
基本思想:
要得到自然数n以内的全部素数,必须把不大于的所有素数的倍数剔除,剩下的就是素数。
给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去......。
步骤例如:
-
列出2以后的所有序列:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 -
标出序列中的第一个素数,也就是2,序列变成:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 -
将剩下序列中,划掉2的倍数,序列变成:
2 3 5 7 9 11 13 15 17 19 21 23 25 -
如果这个序列中最大数小于最后一个标出的素数的平方,那么剩下的序列中所有的数都是素数,否则回到第二步。
-
本例中,因为25大于2的平方,我们返回第二步:
-
剩下的序列中第一个素数是3,将主序列中3的倍数划掉,主序列变成:
2 3 5 7 11 13 17 19 23 25 -
我们得到的素数有:2,3
-
25仍然大于3的平方,所以我们还要返回第二步:
-
序列中第一个素数是5,同样将序列中5的倍数划掉,主序列成了:
2 3 5 7 11 13 17 19 23 -
我们得到的素数有:2,3,5 。
-
因为23小于5的平方,跳出循环.
结论:2到25之间的素数是:2 3 5 7 11 13 17 19 23。
1.判断一个数n是否为素数
首先全部都定义为true,假设全部都是素数,然后从2开始判断是不是素数,是的话进行下一次循环。首先把2*2,然后这个2*2的结果一定不是素数,就把它改成false,然后让这个结果加上2,加上2之后也不是素数,也改成false,直到小于等于规定的最大数的时候停止,继续外层的循环。
一个数乘2之后的结果一定不是素数,因为他有个因子2了,然后这个数再乘之后再不断加上它本身的结果也不是素数,因为这个因子2在不断增大
bool s[maxn];//第i个素数
int prime[maxn];//结果为true表示i为素数
void sieve(int n)
{
int cnt=0;
for(int i=0;i<=n;i++)
prime[i]=true;//先全部置为真
prime[0]=prome[1]=false;//0,1不是素数
for(int i=2;i<=n;i++)//从2开始往后筛
{
if(prime[i])
s[cnt++]=i;//i是下一个素数
for(int j=2*i;j<=n;j+=i)
s[j]=false;
}
}
为了避免重复筛,可以使用另一种算法欧拉筛 :
原则:每一个合数只会被它的最大非自身因数筛选(对应最小质因数),就是保证每个合数只会被筛选一次。
2 加入 p 数组,枚举质数(只有2),筛去质数与 2 的乘积(筛去 4)。3 加入 p 数组(现在有2、3),枚举质数,筛去 3 与质数的乘积(筛去6、9)。4 不是质数,不加入 p 数组,枚举质数,筛去8;此时 4mod2=0 , break。
i | 质数 | 筛去的合数 |
2 | 2 | 4 |
3 | 2,3 | 6,9 |
4 | 2,3 | 8 |
5 | 2,3,5 | 10,15,25 |
......... |
bool a[maxn];
int prime[maxn],cnt;
void sieve(int n)
{
for(int i=2;i<=n;i++)
{
if(a[i]==false)
p[cnt++]=i;//是质数就记录;
for(int j=1;j<=cnt && i*prime[j]<=n;j++)
{
a[i*prime[j]]=true;//标记合数;
if( i%p[j] == 0 )
break;//保证同一个合数不会被多个质数标记。
}
}
例题:
1.蒜头君对既是素数又是回文的数特别感兴趣。比如说 151151 既是素数又是个回文数。回文数是指从左到右读和从右到左读都一样的数。
现在小王想要你帮助他找出某个范围内的素数回文数,请你写个程序找出 aa 跟 bb 之间(包含 aa 和 bb)满足条件的数。
输入格式
输入 aa 和 b(2 \le a < b \le 100,000,000)b(2≤a<b≤100,000,000)。
输出格式
按从小到大输出 a, ba,b 之间所有满足条件的素数回文数,一个数占一行。
Sample 1
Inputcopy Outputcopy 2 200 2 3 5 7 11 101 131 151 181 191
代码如下:
#include<stdio.h>
#define N 10411411
int c[N];
void prime() //筛选素数
{
int i,j;
for(i=0; i<N; i++)
c[i]=1;
c[0]=c[1]=0;
for(i=2; i<N; i++)
{
if(c[i]==1)
{
for(j=2*i; j<N; j+=i)
c[j]=0;
}
}
}
int huiwen(int n)
{
int x=0,s=n;
while(s)
{
x=x*10+s%10;
s=s/10;
}
if(x==n)
return 1;
return 0;
}
int main()
{
int a,b;
scanf("%d%d",&a,&b);
prime();
if(b>10411401)
b=10411401;
for(int i=a; i<=b; i++)
{
if(c[i]==1 &&huiwen(i))
printf("%d\n",i);
}
return 0;
}
2.蒜头君定义两个相差为 22 的素数称为素数对,如 55 和 77,1717 和 1919 等,要求找出所有两个数均不大于 nn 的素数对。
输入格式
一个正整数 n。100001≤n≤10000。
输出格式
所有小于等于 n 的素数对。每对素数对输出一行,中间用单个空格隔开。若没有找到任何素数对,输出
"empty"
。Sample 1
Inputcopy Outputcopy 100 3 5 5 7 11 13 17 19 29 31 41 43 59 61 71 73
代码如下:
#include<bits/stdc++.h>
using namespace std;
int p(int n)
{
if(n==2)
return 1;
for(int i=2;i<=sqrt(n);i++)
if(n%i==0)
return 0;
return 1;
}
int main(void)
{
int n;
cin>>n;
int flag=0;//是否有素数对
for(int i=2;i<=n-2;i++)//防止另一个数超过i+2过n的范围
{
if(p(i)&&p(i+2))
cout<<i<<" "<<i+2<<endl;
flag=1;//有素数对
}
if(flag==0)
cout<<"empty"<<endl;
return 0;
}
3.Given a number n, please count how many tuple(p1, p2, p3) satisfied that p1<=p2<=p3, p1,p2,p3 are primes and p1 + p2 + p3 = n.
Input
Multiple test cases(less than 100), for each test case, the only line indicates the positive integer n (n \leq 10000)n(n≤10000).
Output
For each test case, print the number of ways.
Sample
Inputcopy Outputcopy 3 9 0 2
#include<bits/stdc++.h>
using namespace std;
int p[10010];
void sieve()
{
memset(p,1,sizeof(p));
p[0]=p[1]=0;
for(int i = 2; i <= 10000; i++)
{
if(p[i] == 1)
{
for(int j = 2*i; j <= 10000; j+=i)
{
p[j] = 0;
}
}
}
}
int main()
{
int a[10017];
int n;
sieve();
while(~scanf("%d",&n))
{
int cont = 0;
for(int i = 2; i <= n; i++)
{
if(p[i] == 0)
for(int j = i; j <= n; j++)
{
if(p[j] == 0 && p[n-i-j] == 0)
if(n-i-j>=i && n-i-j>=j)
{
cont++;
}
}
}
printf("%d\n",cont);
}
return 0;
}
4.Everybody knows any number can be combined by the prime number.
Now, your task is telling me what position of the largest prime factor.
The position of prime 2 is 1, prime 3 is 2, and prime 5 is 3, etc.
Specially, LPF(1) = 0.Input
Each line will contain one integer n(0 < n < 1000000).
Output
Output the LPF(n).
Sample
Inputcopy Outputcopy 1 2 3 4 5 0 1 2 1 3
代码如下:
先把所有值都初始化为0,if的第一次判断就直接成立,position 就变成了1。然后数组2的位置,以及后面2的倍数的位置就都是1了,也代表着这些数都不是素数。接下来就是3,数组3的位置也是0,if成立,所以position再加1变成2,然后3以及3的倍数就都变成2。遇到素数了position加一,也相当于是记录素数的个数的一个变量,然后在数组中让这些是素数以及素数的倍数的数,标记为这个position。一个被标记完后可以再次被标记,因为第一次不一定是最大的素数因子,比如10,第一次是因子2,下次再被标记的时候就是因为因子5被标记。
#include<bits/stdc++.h>
using namespace std;
int a[1000010];
int main(void)
{
memset(a,0,sizeof(a));
a[1]=0;
int k=0;
for(int i=2;i<1000000;i++)//不是sqrt
{
if(!a[i])
{
k++;
for(int j=i;j<1000000;j+=i)
a[j]=k;
}
}
int n;
while(scanf("%d",&n)!=EOF)
printf("%d\n",a[n]);
return 0;
}