素数的多种求法(普通筛、线性筛)+牛客寒假集训3H题

判断素数的方法有以下几种:
方法一: 暴力,官方叫试除法
1)根据素数的定义给定一个数n,i从2开始取值知道n-1,若n%i != 0,那么n即为素数。进一步思考——有必要遍历到n-1吗?
我们知道,除了1以外,任何合数最小的因子是2,那么最大的因子就是n/2,所以遍历到n/2就足够了。

int n;
if(n==1 || n==0)	printf("0");
for(int i=2;i<=n/2;i++){
	if(n%i==0){
		printf("0");
		return 0;
	}
}
printf("1");

2)在以上的基础上,有数学证明表明只需遍历到 n \sqrt{n} 即可 ,具体证明可百度,不赘述,代码类似上面的


高级用法来了~

——普通筛和线性筛
1)普通筛——埃拉托斯特尼(Eratosthenes)筛法(时间复杂度 nln(ln n) )

简单来讲,就是把不大于 n \sqrt{n} 的所有的素数倍数剔除,剩下的就是素数

举个栗子:求出16以内的所有素数(一个✳表示划去过一次)

  • 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    先判断出了第一个素数:2,那么下一步,划去2的倍数(本身除外)
  • 2 3 4* 5 6* 7 8* 9 10* 11 12* 13 14* 15 16*
    如果这个序列中最大数小于最后一个标出的素数的平方,那么剩下的序列中所有的数都是素数,否则继续第二步。在本例中15大于2的平方,所以继续,划去3的倍数
  • 2 3 4* 5 6** 7 8* 9* 10* 11 12** 13 14* 15* 16*
    13大于3的平方,继续划去5的倍数
  • 2 3 4* 5 6* 7 8* 9 10** 11 12* 13 14* 15* 16*
    13小于5的平方,故16以内的素数是2 3 5 7 11 13,程序结束,输出所有没有星号标记过的数字。
void Prime(long long n){
	for(i=0;i<=n;i++){
		prime[i] = 1;
	}
	//上面的错误写法:memset(prime,1,sizeof(prime));
	//假设全为素数,初始化 
	prime[0] = prime[1] = 0;
	//0和1均不是素数 
	for(i=2;i<sqrt(n);i++){
		if(!prime[i])	continue;
		//划去素数i的倍数(不包括本身,故从2i)
		for(j=i*2;j<=n;j+=i){
			prime[j] = 0;
		}
	}
	for(i=0;i<=n;i++){
		if(prime[i]==1){
			cout << i <<" ";
		}
	}
}

(贴一个小贴士:memset在初始化int类型数组时只能初始化为0或-1,不能初始化为1)
2)线性筛
在介绍上面的埃氏筛时,时间复杂度为O( nlg(lgn) ),而线性筛与之相比,时间复杂度为线性的,即O(n)。
在求解普通筛时,我们发现,有一些数字被划去了两次(即被重复赋值),所以复杂性稍微高一点。
在上例中,6和12被剔除了两次,重复赋值。防止重复赋值,线性筛用每一个数的最小质因数来筛去(每一个合数的最小质因数唯一)
如果不明白,那么举个栗子:30=2x3x5,在埃氏筛中,它会被2,3,5各筛一次,但是在线性筛中,只会被2筛一次,因为2是30的最小质因数。

//该数组元素下标与自然数一一对应,其值代表是否为素数,是为0,不是为1
//如a(2)应该为0,因为2是素数,a(4)=1,因为4不是素数
//prime数组存储已求的素数,a数组用0/1来判断该数是否被划去,num记录储存的素数数目
memset(a,0,sizeof(a));
memset(prime,0,sizeof(prime));
a[0]=a[1]=1;num=0;
for(int i=2;i<n;i++){
	if(!a[i])	prime[num++]=i;
	for(int j=0;j<num && i*prime[j]<n;j++){
	//筛去以prime[j]为最小素因子的数
		a[i*prime[j]]=1;
		//核心
		if(!i%prime[j])	break;
	}
}
for(int i=0;i<num;i++){
	printf("%d ",prime[i]);
}

(太菜了,这个线性筛花了好长时间才弄明白,最后主要还是靠一步步debug观察数组的变化,再结合代码才理解的orz)


再贴一道牛客寒假算法基础集训营3的H题,思路很简单,结合素数筛判断即可。
题目传送门
ac代码如下:

#include<cstdio>
#include<string.h>
#include<iostream>
#include<cmath>
const int Max = 100050;
using namespace std;
int s[Max],a[Max],prime[Max];
void Prime(long long n){
    for(int i=1;i<=n;i++){
        prime[i] = 1;
    }
    //假设全为素数,初始化
    prime[0] = prime[1] = 0;
    //0和1均不是素数
    for(int i=2;i<=sqrt(n);i++){
        if(!prime[i])   continue;
        //划去素数i的倍数(不包括本身,故从2i)
        for(int j=i*2;j<=n;j+=i){
            prime[j] = 0;
        }
    }
    prime[1]=1;
}
  
int main(){
    int n,k,index;
    cin>>n >> k;
    memset(s,0,sizeof(s));
    Prime(n); //打表
    for(int i=1;i<=n;i++){
        int count=0;
        for(int j=1;j*j<=i;j++){
            if(i%j==0){
                if(!prime[j])
                    count++; 
                if(!prime[i/j])
                    count++;
                if(j*j == i && !prime[j])
                    count--;
            }
        }
        s[count]++;
    }
    for(int i=1;i<=k;i++){
        cin >> index;
        cout << s[index]<<endl;
    }
    return 0;
}
发布了13 篇原创文章 · 获赞 1 · 访问量 449

猜你喜欢

转载自blog.csdn.net/amino_acid0617/article/details/102023514