1. 程式人生 > >快速篩素數(埃式篩+線性篩+Miller_Rabin演算法)

快速篩素數(埃式篩+線性篩+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<tmp;i+=6)if(num%i==0||num%(i+2)==0)
  return 0;/*排除所有,剩餘的是質數*/ return 1;}

最後判斷6的倍數兩側的數是否是質數的時候,假如不是質數,合數一定可以由質數和合數相乘表示出來。

而質數只能是6的倍數,列舉6的倍數判斷兩側即可。

這是高效率判斷素數的方法。

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

參考:

埃式篩

採用了一個簡單的原理:質數的>1倍數一定是合數,合數一定是由素數和合數相乘可以得到的,所以每次得到新的素數將其所有倍數篩去,最後剩下的都是素數。

const int MAXN = 1000000;  
void get_list()  
{  
    int i, j;  
    for (i=0; i<MAXN; i++) prime[i] = 1;  
    prime[0] = prime[1] = 0;  
    for (i=2; i<MAXN; i++)  
    {  
        if (!prime[i]) continue;  
        for (j=i*2; j<MAXN; j+=i) prime[ j ] = 0;  
    }  
}

1表示為素數,0表示合數。全部初始化為素數,順序篩去合數,但這樣會重複篩去,2*3=6和3*2=6會重複篩。

但並不代表這樣篩素數就很慢。

PS:上述過程複雜度為n*lnlnn,接近線性。

線性篩(尤拉篩法)

原理:

1、埃式篩減少重複篩去的部分。

2、任何一個合數等於一個質數和合數的乘積。

A,B,C是合數,P1,P2是質數。

A=P1*B

B=P2*C

A=P1*P2*C

A=(P1*C)*P2

如果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演算法

詳細解釋見上。

主要思想是利用費馬小定理和二次探測定理進行判定。

然後將p-1分解成2^k*t的形式,因為p是質數,肯定為奇數。

利用快速冪的原理自乘判斷。

由於不是完全正確,需要多次判斷提高正確率。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<stack>
#include<vector>
#include<set>
#define LL long long

using namespace std;

inline int read()
{
	char c=getchar();
	int x=0,f=1;
	while(c<'0'||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");}
}