NumberTheory

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

2019-09-26  本文已影响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;
}
上一篇下一篇

猜你喜欢

热点阅读