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


    在平常做数论的题时,大部分都要涉及到判定(筛)素数吧,而今天我将给大家展示不同算法的判定(筛)素数方法.....例题: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+=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<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 }

    埃氏筛

    思想

    任意整数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     }
  • 相关阅读:
    2017《Java技术》预备作业 计科1501 杨欣蕊
    Java技术预备作业02杨欣蕊
    系统无法从光盘启动
    动态数组ArrayList的使用
    dbgrid数据显示和数据源不同
    异步任务判断服务器是否开启
    Java字符串格式化
    思科2960 监听端口设置
    64位win7安装jdk和eclipse
    Delphi临界区的使用
  • 原文地址:https://www.cnblogs.com/hualian/p/11236483.html
Copyright © 2020-2023  润新知