c语言 快速筛质数,快速筛素数(埃式筛+线性筛+Miller_Rabin算法)

在CF上做到一道核心是需要筛出1~n所有素数的题目,然后刚好又没学过,就学习了快速筛素数的办法,基础的n根号n的算法这里大家每个人都知道吧QAQ,就不讲了,好像还是C语言上机说过的题目。

首先给大家介绍一下一个比较简单的判断素数的方法:

利用性质:大于等于5的质数一定和6的倍数相邻。

bool isPrime(int num){/*不在6的倍数两侧的一定不是质数*/

if(num==1)return 0;

if(num==2||num==3) return 1;

/*不在6的倍数两侧的一定不是质数*/

if(num%6!=1&&num%6!=5) return 0;

int tmp=sqrt(num);/*在6的倍数两侧的也可能不是质数*/

for(int i=5;i

最后判断6的倍数两侧的数是否是质数的时候,假如不是质数,合数一定可以由质数和合数相乘表示出来。

而质数只能是6的倍数,枚举6的倍数判断两侧即可。

这是高效率判断素数的方法。

接下来是高效率求1~n所有素数的方法:

参考:

https://www.luogu.org/problemnew/solution/P3383

http://www.cnblogs.com/zwfymqz/p/8150969.html

埃式筛

采用了一个简单的原理:质数的>1倍数一定是合数,合数一定是由素数和合数相乘可以得到的,所以每次得到新的素数将其所有倍数筛去,最后剩下的都是素数。

const int MAXN = 1000000;

void get_list()

{

int i, j;

for (i=0; i

1表示为素数,0表示合数。全部初始化为素数,顺序筛去合数,但这样会重复筛去,2*3=6和3*2=6会重复筛。

但并不代表这样筛素数就很慢。

证明:https://blog.csdn.net/OIljt12138/article/details/53861367

PS:上述过程复杂度为n*lnlnn,接近线性。

线性筛(欧拉筛法)

原理:

1、埃式筛减少重复筛去的部分。

2、任何一个合数等于一个质数和合数的乘积。

A,B,C是合数,P1,P2是质数。

9bf057c25abbd25ec064a5f03815ae6b.png

63619ef44dd5ebd9b03678aee89d70e9.png

949bc9c09861e01f1a40ad3b9053acd7.png

2fbe3d5ad597cadc4b57b62012def333.png

如果A表示成为合数和质数的乘积,合数可以再分解,使得最后质数越来越小,合数越来越大。

我们只取最小质因数来筛素数,能避免很多重复情况。

核心代码:

if(i%prime[j]==0) break;

即比一个合数大的质数和该合数的乘积可用一个更大的合数和比其小的质数相乘得到,prime表示素数。

i*prime=素数*倍数,假设i可以表示为更小的素数,说明此时的 合数不够大,也就是i不够大,说明之后还会筛一次,我们只需要筛合数最大,且素数最小的乘积得出来的合数,就能保证不会重复。

从这里跳出循环,是因为从此之后的所有元素中的合数都可以换成较小的prime[j]素数,也就是不是最小质因数。

void get_list(){

for(int i=2;i<=maxn;i++){

if(!is_not_pr[i]) prime[++tot]=i;

for(int j=1;j<=tot&&i*prime[j]<=maxn;j++){

is_not_pr[i*prime[j]]=1;//合数标为1,同时,prime[j]是合数i*prime[j]的最小素因子

if(i%prime[j]==0) break;//即比一个合数大的质数和该合数的乘积可用一个更大的合数和比其小的质数相乘得到

}

}

}

能这样表示完全是因为:这样得出来的合数一定是由最小质因数和合数乘积得来,且显然合数最后一定大于质数。枚举最大合数的过程中,最小质数一定已经的出来了。

没有重复的情况下:0(n)的复杂度。

Miller_Rabin算法

http://www.cnblogs.com/zwfymqz/p/8150969.html#undefined

详细解释见上。

主要思想是利用费马小定理和二次探测定理进行判定。

然后将p-1分解成2^k*t的形式,因为p是质数,肯定为奇数。

利用快速幂的原理自乘判断。

由于不是完全正确,需要多次判断提高正确率。

#include#include#include#include#include#include#include#include#define LL long long

using namespace std;

inline int read()

{

char c=getchar();

int x=0,f=1;

while(c'9'){if(c=='-')f=-1;c=getchar();}

while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();

return x*f;

}

int N,M,Test[10]={2,3,5,7,11,13,17};

LL pow(int a,int p,int mod)

{

int base=1;

for(;p;p>>=1,a=(1LL*a*a)%mod)

if(p&1)base=(1LL*base*a)%mod;

return base % mod;

}

bool Query(int P)

{

if(P==1)return 0;

int t=P-1,k=0;

while(!(t&1))k++,t>>=1;

for(int i=0;i<4;i++)

{

if(P==Test[i])return 1;

LL a=pow(Test[i],t,P),next=a;

for(int j=1;j<=k;j++)

{

next=(a*a)%P;

if(next==1&&a!=1&&a!=P-1)return 0;//a=p-1就代表a是最后一个,就不存在next了

a=next;

}//二次探测定理判断

if(a!=1)return 0;//费马小定理

}

return 1;

}

int main()

{

N=read();M=read();

while(M--)

{if(Query(read()))printf("Yes\n");

else printf("No\n");}

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值