【素数判定——暴力到高效】

在平常做数论的题时,大部分都要涉及到判定(筛)素数吧,而今天我将给大家展示不同算法的判定(筛)素数方法.....例题: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-16x+1的形式,对于循环中6i-16i6i+1,6i+2,6i+3,6i+4其中如果n能被6i,6i+26i+4整除,则n至少得是一个偶数,

  但是6x-16x+1的形式明显是一个奇数,故不成立;另外,如果n能被6i+3整除,则n至少能被3整除,

  但是6x能被3整除,故6x-16x+1(即n)不可能被3整除,故不成立。综上,循环中只需要考虑6i-16i+1的情况,

  即循环的步长可以定为6,每次判断循环变量kk+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++)
 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<px21(modp);可以推得x210(modp)p|x21p|(x1)(x+1)x<p,所以如果p|(x1)的话,x1=0,x=1,或p|x+1,则x=p1。因此,就有了Miller-Rabin测试。

Miller-Rabin

   有了二次探测定理,我们试着进行341的以2为底的费马测试。23401(mod341)如果341是素数,那么也满足二次探测定理,也就是21701(mod341)而170还是个偶数,可以继续进行二次探测定理。这时它就凉了,因为28532(mod341),而它没有通过二次探测定理,所以341不是个素数。

    同时,因为费马小定理没有要求底为什么,所以只以2为底肯定会放过一些漏网之鱼,我们应该多选一些数为底,这样才能使判断的正确性提高。不过这个底最好选择素数(不知道为什么,可能与答案的模数大都为质数一样吧...),来保证正确性。同时,在学习这个算法时,网上会有一写神奇的结论,比如选3个特定的底2,7,61,就可以通过小于4,759,123,141的所有素数的测试,而选latex2,3为底,可以通过1,373,653以内的测试。因此很多人都喜欢随机几个数作为底,而题目给出的质数也不一样,这就是靠碰运气了。不过上面分析过,它的错误率只有约4k,所以出题人在不知道你的底数的情况下,正确率是特别高的。

 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 }

猜你喜欢

转载自www.cnblogs.com/hualian/p/11236483.html