美文网首页NumberTheory
素数筛,线性筛(欧拉筛),莫比乌斯函数筛,前n个数的约数个数筛

素数筛,线性筛(欧拉筛),莫比乌斯函数筛,前n个数的约数个数筛

作者: 雨落八千里 | 来源:发表于2019-09-26 23:24 被阅读0次

    问题:给出一个数n,输出1~n之间的素数

    素数筛

    1. 埃拉托斯特尼筛法
    • 每次消去2、3、4、5 、6 、、、、、、的倍数,直到没有可消的为止,剩下的数字则为素数;
      每次考虑消去的第一个数为i*i,因为(i-1)*i已经在i-1为基数,i为倍数的时候已经判断了;以此类推,当i的倍数小于i时都被判断了,于是只要判断i的倍数大于等于i的时候
      时间复杂度为O(n*logn);空间复杂度O(n)
    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int M=1000+10;
    int vis[M];
    int cnt;
    void getprim( )
    {
       for(int i=2;i*i<=M;i++)
       {
           int k=i;
           for(int j=i*i;j<=M;j=i*k)
           {
               vis[j]=1;
               k++;
           }
       }
    }
    int main( )
    {
       memset(vis,0,sizeof(vis));
       vis[1]=1;
       getprim( );
       for(int i=1;i<M;i++)
       {
           if(vis[i]==0)
           {
               cout<<i<<endl;
           }
       }
       return 0;
    }
    
    1. 欧拉筛法
    • 在埃拉托斯特尼筛算法中有很多数都重复操作vis[j]=1,因为2的倍数如16,20,24已经判断了,但是在4的倍数也有16,20,24然后又判断一次所以就有重复的
      欧拉筛法
      每次用已筛出来的质数去筛更大的数,每个合数只被它最小的质因子筛掉。
      试想,如果2*6筛了12之后还没break,而是用3*6筛掉18,那么18还会被2*9筛一次,就重复了而根本原因就是6有2这个因子,而3*6筛掉的数一定也有2这个因子,3*6这个数应该被2这个因子筛掉,而不是3
    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int M=1000+10;
    int vis[M];
    int prim[M];
    int cnt;
    void getprim( )
    {
       memset(vis,0,sizeof(vis));
       memset(prim,0,sizeof(prim));
       for(int i=2;i<=M;i++)
       {
           if(!vis[i])//在2~i-1之间没有标记,意味着没有它的因数,那么它是质数
           {
               prim[cnt++]=i;
           }
           for(int j=0;j<cnt&&i*prim[j]<=M;j++)
           {
               vis[i*prim[j]]=1;//剔除每个质数的i倍的那个数
               if(i%prim[j]==0)//保证每个合数只被它最小的质因子剔除
               {
                   break;
               }
           }
       }
    }
    int main( )
    {
       cnt=0;
       getprim( );
       for(int i=0;i<cnt;i++)
       {
           cout<<prim[i]<<endl;
       }
       return 0;
    }
    

    问题:给出一个数n,输出1~n之间的每个数x的欧拉函数φ(x)

    欧拉筛

    • 根据欧拉函数的相关定理
      phi[1]=1;phi[p]=p-1其中p为质数
      phi[ab]=phi[a]*phi[b]其中a,b互质

    • φ(x)=x*(1-1/p_1)*(1-1/p_2)*...*(1-1/p_k)

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int M=1000+10;
    int vis[M];
    int prim[M];
    int phi[M];
    int cnt;
    void getprim( )
    {
        memset(vis,0,sizeof(vis));
        memset(prim,0,sizeof(prim));
        memset(phi,0,sizeof(phi));
        phi[1]=1;
        for(int i=2;i<=M;i++)
        {
            if(!vis[i])
            {
                prim[cnt++]=i;
                phi[i]=i-1;
            }
            for(int j=0;j<cnt&&i*prim[j]<=M;j++)
            {
                vis[i*prim[j]]=1;
                if(i%prim[j]==0)
                {
                    phi[i*prim[j]]=phi[i]*prim[j];
                    break;
                }
                else
                {
                    phi[i*prim[j]]=phi[i]*(prim[j]-1);
                }
                
            }
        }
    }
    int main( )
    {
        cnt=0;
        getprim( );
        // for(int i=0;i<cnt;i++)
        // {
        //     cout<<prim[i]<<endl;
        // }
        // cout<<endl;
        for(int i=1;i<M;i++)
        {
            cout<<phi[i]<<endl;
        }
        return 0;i
    }
    
    • i\%prim[j]==0意味着prim[j]这个质数是i的因子

    因为φ(i)=i*(1-1/p_1)*(1-1/p_2)*...*(1-1/p_k)
    所以φ(i*prim[j])=i*prim[j]*(1-1/p_1)*(1-1/p_2)*...*(1-1/p_k)=φ(i)*prim[j]

    因为i*prim[j]分解出来的p一定与i分解出来的p相同(其中p==prim[j]的指数不一样)
    所以phi[i*prim[j]]=prim[j]*phi[i]

    • i\%prim[j]!=0意味着iprim[j]互质
      根据欧拉函数的积性函数
      phi[i*prim[j]]=phi[i]*(prim[j]-1)

    莫比乌斯函数筛

    • μ(n) ——莫比乌斯函数,关于非平方数的质因子数目。
      μ[n]=\begin{cases} 1 \ \ \ \ \ \ n==1\\ (-1)^k\ \ \ \ \ \ \ n没有平方数因数且n=p_1*p_2*...*p_k\\ 0 \ \ \ \ \ \ \ \ \ \ n有大于1的平方数因数\end{cases}

    1)莫比乌斯函数μ(n)的定义域是N;
    2)μ(1)=1;
    3)当n存在平方因子时,μ(n)=0;
    4)当n是素数或奇数个不同素数之积时,μ(n)=-1;
    5)当n是偶数个不同素数之积时,μ(n)=1。

    • 这个做起来比欧拉函数容易,在过程上,若i为素数则μ[i] = -1,若i % prim[j] == 0,则μ[i * prim[j]] = 0,显然prim[j]就是它的平方因子,否则μ[i * prim[j]] = -μ[i],因为多了一个质因子
    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int M=1e3+10;
    int mob[M];
    int prim[M];
    int vis[M];
    int cnt;
    void getmob( )
    {
        memset(vis,0,sizeof(vis));
        memset(prim,0,sizeof(prim));
        memset(mob,0,sizeof(mob));
        mob[1]=1;
        for(int i=2;i<M;i++)
        {
            if(!vis[i])//质数
            {
                prim[cnt++]=i;
                mob[i]=-1;//质数为-1
            }
            for(int j=0;j<cnt&&i*prim[j]<M;j++)
            {
                vis[i*prim[j]]=1;
                if(i%prim[j]==0)
                {
                    mob[i*prim[j]]=0;//i%prim[j]==0,意味着i中有因子prim[j],所以i*prim[j]中有平方因子prim[j]
                    break;
                }
                else
                {
                    mob[i*prim[j]]=-mob[i];
                }
                
            }
        }
    }
    int main( )
    {
        cnt=0;
        getmob( );
        for(int i=1;i<M;i++)
        {
            cout<<mob[i]<<endl;
        }
        return 0;
    }
    

    前n个数的约数个数筛

    1. 一个大于1的正整数N都有一个唯一分解定理:
      N=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n} (p_i是质数)
    2. 算术基本定理中,根据拆分后的质因子的指数,我们可以求出每个N的约数的个数。所以N的约数个数可以表示为:
      d(N)=(1+k_1)*(1+k_2)*...*(1+k_n)
    3. 根据这个式子,我们可以用线性筛去筛出当前 N 的约数个数。
      筛的过程中,我们需要保存下最小质因子的个数。
    证明:

    首先规定:
    d[i]表示i的约数个数
    num[i]表示i的最小质因子的个数(指数)
    prim[j]表示第j个质数

    • 情况1:
      i是一个质数,那么d[i]=(1+1)=2(约数为1和自己本身i)
      只有一个质因子就是自身(i) ,所以num[i]=1
    • 情况2:
      i\%prim[j]!=0
      i\%prim[j]!=0说明i中没有prim[j]这个质因子,但是i*prim[j]有这个质因子,i的唯一分解定理i=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}其中(p_i!=prim[j])
      所以i*prim[j]=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}*(prim[j])^1
      d[i]=(1+k_1)*(1+k_2)*...*(1+k_n)
      所以d[i*prim[j]]=(1+k_1)*(1+k_2)*...*(1+k_n)*(1+1)
      所以d[i*prim[j]]=d[i]*2
      i*prim[j]的最小质因子是什么呢!因为prim[j]是从小到大枚举并且在线性筛中每个只会被自己最小的质因子剔除
      所以i*prim[j]的最小质因子就是prim[j]
      所以num[i*prim[j]]=1

    情况3:
    i\%prin[j]==0
    i\%prim[j]==0说明i中有prim[j]质因子,其中prim[j]肯定是i的最小质因子,因为prim是从小到大枚举
    i=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}
    那么i*prim[j]=p_1^{k_1+1}*p_2^{k_2}*...*p_n^{k_n}

    d[i]=(1+k_1)*(1+k_2)*...*(1+k_n)
    d[i*prim[j]]=(1+k_1+1)*(1+k_2)*...*(1+k_n)

    num[i*prim[j]]=num[i]+1
    所以
    d[i*prim[j]]=d[i]/(1+num[i])*(1+num[i*prim[j]])

    参考二连
    「参考」 「参考」

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int M=1e3+10;
    int d[M];
    int prim[M];
    int num[M];//i的最小质因子的个数(指数)
    int vis[M];
    int cnt;
    void getd( )
    {
        memset(vis,0,sizeof(vis));
        memset(prim,0,sizeof(prim));
        memset(d,0,sizeof(d));
        memset(num,0,sizeof(num));
        d[1]=1;
        for(int i=2;i<M;i++)
        {
            if(!vis[i])
            {
                prim[cnt++]=i;
                d[i]=2;
                num[i]=1;
            }
            for(int j=0;j<cnt&&i*prim[j]<M;j++)
            {
                vis[i*prim[j]]=1;
                if(i%prim[j]==0)
                {
                    num[i*prim[j]]=num[i]+1;
                    d[i*prim[j]]=d[i]/(num[i]+1)*(num[i*prim[j]]+1);
                    break;
                }
                else
                {
                    num[i*prim[j]]=1;
                    d[i*prim[j]]=d[i]*2;
                }
            }
        }
    }
    int main( )
    {
        cnt=0;
        getd( );
        for(int i=1;i<M;i++)
        {
            cout<<d[i]<<endl;
        }
        return 0;
    }
    

    相关文章

      网友评论

        本文标题:素数筛,线性筛(欧拉筛),莫比乌斯函数筛,前n个数的约数个数筛

        本文链接:https://www.haomeiwen.com/subject/idksyctx.html