在平常做数论的题时,大部分都要涉及到判定(筛)素数吧,而今天我将给大家展示不同算法的判定(筛)素数方法.....例题:P3383 【模板】线性筛素数
直观判断法
这个比较暴力,直接从2枚举到N-1,再看能不能整除判断即可,也就是看这个数除了1和自己本身还有没有其他的因数
bool prime(int n); { if(n==1)return false; if(n==2)return true; for(int i=2;i<n;i++) if(n%i==0)return false; return true; }
时间复杂度是O(N)
但是当N较大的时候,肯定会超时啊,所以我们需要一些优化
优化1
我们可以看出,因数总是成对出现的,而一对的两个数,都以sqrt(N)为分界线,一边一个(除了sqrt(N)自己),所以我们只需要从2枚举到sqrt(N)即可
1 bool prime(int n); 2 { 3 if(n==1)return false; 4 if(n==2)return true; 5 for(int i=2;i*i<=n;i++) 6 if(n%i==0)return false; 7 return true; 8 }
时间复杂度是O(sqrt(N))
虽然已经很优了,但是直到我打开了洛谷题解。
我们来看看质数分布的规律:大于等于5的质数一定和6的倍数相邻。例如5和7,11和13,17和19等等;
证明:令x≥1,将大于等于5的自然数表示如下: ......6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,6(x+1),6(x+1)+1............
可以看到,不在6的倍数两侧,即6x两侧的数为6x+2,6x+3,6x+4...由于2(3x+1),3(2x+1),2(3x+2),
所以它们一定不是素数,再除去6x本身,显然,素数要出现只可能出现在6x的相邻两侧。
所以,基于以上条件,我们假如要判定的数为n,则nn必定是6x-1或6x+1的形式,对于循环中6i-1,6i,6i+1,6i+2,6i+3,6i+4其中如果n能被6i,6i+2,6i+4整除,则n至少得是一个偶数,
但是6x-1或6x+1的形式明显是一个奇数,故不成立;另外,如果n能被6i+3整除,则n至少能被3整除,
但是6x能被3整除,故6x-1或6x+1(即n)不可能被3整除,故不成立。综上,循环中只需要考虑6i-1和6i+1的情况,
即循环的步长可以定为6,每次判断循环变量k和k+2的情况即可(转自洛谷题解第一篇)
代码如下
1 bool prime(int x) 2 { 3 if(n==1)return false; 4 if(n==2||n==3)return true; 5 if(n%6!=1&&n%6!=5)return false; 6 for(int i=5;i*i<=x;i+=6) 7 { 8 if(n%i==0||n%(i+2)==0)return false; 9 } 10 return true; 11 }
时间复杂度是O(sqrt(N)/3)
所以作为最基本的小白,这种判定还是要掌握的
Miller-Rabin
(思路来源自https://www.cnblogs.com/wjyyy/p/note1.html)
这个算法是由Miller和Rabin根据费马测试优化过来的(费马小定理的逆定理)
a^(p-1)≡1(mod p)
都知道,只有p为质数才成立,然后很尴尬的341就出来了,虽然满足以2为底的费马小定理,但是.....341=11*31
然后,他俩闲着没事就......
二次探测定理
有一个叫二次探测定理的东西,可以有效地提升费马小定理的正确性。如果对于素数p,有正整数x<p且x2≡1(modp);可以推得x2−1≡0(modp)⇒p|x2−1⇒p|(x−1)(x+1)而x<p,所以如果p|(x−1)的话,x−1=0,x=1,或p|x+1,则x=p−1。因此,就有了Miller-Rabin测试。
Miller-Rabin
有了二次探测定理,我们试着进行341的以2为底的费马测试。2340≡1(mod341)如果341是素数,那么也满足二次探测定理,也就是2170≡1(mod341)而170还是个偶数,可以继续进行二次探测定理。这时它就凉了,因为285≡32(mod341),而它没有通过二次探测定理,所以341不是个素数。
同时,因为费马小定理没有要求底为什么,所以只以2为底肯定会放过一些漏网之鱼,我们应该多选一些数为底,这样才能使判断的正确性提高。不过这个底最好选择素数(不知道为什么,可能与答案的模数大都为质数一样吧...),来保证正确性。同时,在学习这个算法时,网上会有一写神奇的结论,比如选3个特定的底2,7,61,就可以通过小于4,759,123,141的所有素数的测试,而选latex2,3为底,可以通过1,373,653以内的测试。因此很多人都喜欢随机几个数作为底,而题目给出的质数也不一样,这就是靠碰运气了。不过上面分析过,它的错误率只有约4−k,所以出题人在不知道你的底数的情况下,正确率是特别高的。
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 long long POW(ll x,ll y,ll p) 5 { 6 ll ans=1; 7 while(y) 8 { 9 if(y%2==1)ans*=x; 10 ans%=p; 11 x*=x; 12 x%=p; 13 y>>=1; 14 } 15 return ans; 16 } 17 bool check(ll x,ll y,ll p) 18 { 19 ll ans=POW(x,y,p); 20 if(ans!=1&&ans!=p-1)return false; 21 if(ans==p-1)return true; 22 if(ans==1&&y%2==1)return true; 23 return check(x,y>>1,p); 24 } 25 bool mr(ll x) 26 { 27 if(x<=1)return false; 28 if(x==2||x==7||x==61||check(2,x-1,x)&&check(7,x-1,x)&&check(61,x-1,x)) 29 return true; 30 return false; 31 } 32 int main() 33 { 34 ll n,m; 35 cin>>n>>m; 36 for(int i=1;i<=m;i++) 37 { 38 scanf("%lld",&n); 39 if(mr(n))cout<<"Yes"<<endl; 40 else cout<<"No"<<endl; 41 } 42 return 0; 43 }
埃氏筛
思想
任意整数x的倍数都不是素数
我们可以从小到大枚举n以内的质数,对不超过n的倍数进行标记剩下未标记的数即为质数。
1 for(int i=2;i<=b;i++) 2 { 3 if(a[i]==0) 4 for(int j=2*i;j<=n;j+=i) 5 a[j]=1; 6 }
但是一个数可能被多个质数重复标记,所以我们从i*i开始枚举
1 for(int i=2;i<=b;i++) 2 { 3 if(a[i]==0) 4 for(int j=i*i;j<=n;j+=i) 5 a[j]=1; 6 }
时间复杂度为O(N*loglogn),效率已经接近线性
欧拉筛(真 线性筛)
1 for(int i=2;i<=n;i++) 2 { 3 if(flag[i]==0) 4 { 5 len++; 6 prime[len]=i; 7 } 8 for(int j=1;j<=len&&i*prime[j]<=n;j++) 9 { 10 flag[i*prime[j]]=1; 11 if(i%prime[j]==0)break; 12 } 13 }