数论常用内容——素数

在做数论题过程中,素数出现的频率很高,在基础题和中档题甚至很多高难度的题里面都很常见,这篇博客就来对素数及其使用做一个小小的总结

素数

首先,我们应该明确,素数的定义:

素数(prime number)又称质数,有无限个。素数定义为在大于1的自然数中,除了1和它本身以外不再有其他约数的数称为素数。

素数个数估计

既然素数有无限多个,那么n以内的素数有多少个呢?

可以通过如下方式来估计n以内的素数个数

素数个数估计:π(n)≈x/ln(n)

筛素数

知道了有这么多的素数,那我们如何得到素数呢?

直接一个一个遍历判断显然是不可取的,在竞赛中,我们一般采用打素数表的方法来解决这个问题,不知道哪位高人给打素数表起了筛素数这个名字,个人感觉十分贴切。筛法有很多,这里我只介绍一下时间效率最高的欧拉筛法,请看代码

const int MAXN = 10000000 + 10;
bool is_prime[MAXN];//素数筛,true表示该数为素数
int prime[MAXN];//素数表,这里面存了MAXN以内的所有素数
int tol;//素数个数统计

void PrimeTable(){
    //初始化
    memset(is_prime,true,sizeof(is_prime));
    tol=0;
    //从最小的素数2开始筛
    for(int i=2;i//如果是素数就加入素数表中
        if(is_prime[i]) prime[tol++]=i;
        //进行筛素数操作
        for(int j=0;jif(i*prime[j]>MAXN)break;
            //把当前数字与素数表中的每个数字的乘积全部筛掉(这些因为有其他因子了,所以不可能是素数)
            is_prime[i*prime[j]]=false;
            //这里很关键,它保证了每一个合数只被筛一遍(大家可以在纸上模拟一下这个过程,就会有深刻的理解)
            if(i%prime[j]==0)break;
        }
    }
}

素数的应用

大数据小区间筛素数个数

模拟筛法筛区间内素数个数,先打表,再根据表进行筛素数操作

typedef long long ll;

const int MAXN = 1000000+10;

bool is_prime[MAXN];//true的为合数,false的为素数
int prime[MAXN/10];//存素数
bool num[MAXN];
int tol;
void find_prime(){
    memset(is_prime,false,sizeof(is_prime));
    tol=0;
    is_prime[1]=1;
    for(int i=2; iif(!is_prime[i]){
            prime[tol++]=i;
            for(int j=i*2; j1;
        }
    }
}

int main(){
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int T;
    cin>>T;
    find_prime();
    for(int cas=1; cas<=T; cas++){
        int a,b;
        scanf("%d %d",&a,&b);
        int n=b-a;
        memset(num,false,sizeof(num));
        for(int i=0;iint j=0;
            if(a%prime[i]!=0)
                j=prime[i]-a%prime[i];//找到第一个需要筛掉的数(j+a)%prime[i]==0
            if(a<=prime[i])
                j+=prime[i];//(j+a)/prime[i]==1,则(j+a)是素数,向下推一个
            for(;j<=n;j+=prime[i])
                num[j]=true;
        }
        int ans=0;
        for(int j=0;j<=n;j++){
            if(!num[j]) ans++;
        }
        if(a==1) ans--;
        printf("Case %d: %d\n",cas,ans);
    }
    return 0;
}

素因数分解

这里有两种素因数分解方法

首先,第一种朴素分解方法,这种方法适用于数字比较小的时候的因数分解

有两种不同的实现方式,大家可以任选其一

int a[MAXN/10];//保存因子
int b[MAXN/10];//保存因子个数
int tol2;
void sbreak(ll n){
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    tol2=0;
    for(int i=0; prime[i]*prime[i]<=n&&i//此处的tol为筛出的素数总数{
        if(n%prime[i]==0){
            a[tol2]=prime[i];
            while(n%prime[i]==0){
                b[tol2]++;
                n/=prime[i];
            }
            tol2++;
        }
    }
    if(n!=1){
        a[tol2]=n;
        b[tol2++]=1;
    }
}

const int M = 1000010;
int facs[M]; //n的所有质因子
int tot; //n的质因子个数
int kind; //质因子种数
struct Factor{
    int val; //该质因子的值
    int num; //该质因子的个数
}factor[M];

void solve(int n){
    tot=0;
    for(int i=2;i*i<=n;i+=2){
        while(!(n%i))
            n/=i,facs[tot++]=i;
        if(i==2) --i;
    }
    if(n>1) facs[tot++]=n;
}

void collect(int n){
    solve(n);
    kind=0;
    factor[kind].val=facs[0];
    factor[kind].num=1;
    kind++;
    for(int i=1;iif(facs[i]!=facs[i-1]){
            factor[kind].val=facs[i];
            factor[kind].num=1;
            kind++;
        }
        else{
            factor[kind-1].num++;
        }
    }
}

第二种,Pollard_rho因数分解,这种方法适用于数字比较大时的因数分解

这种素数分解方法涉及MillerRabin素数测试的相关知识,大家可以到网上其他文章中了解一下相关知识,嫌麻烦也没关系,代码里有注释,大家也可以看代码直接理解

#include 
#define LL long long
#define case 10//一般8~10就够了
using namespace std;

LL Get_rand(LL n){
    return 2 + rand()%(n-2);
}

LL Mul(LL a,LL b,LL m){
    LL ans = 0;
    while(b){
        if(b & 1){
            ans = (ans + a) % m;
            b--;
        }
        else{
            a = (2 * a) % m;
            b >>= 1;
        }
    }
    return ans%m;
}

LL Quick_mod(LL a,LL n,LL m){
    LL ans = 1;
    while(n){
        if(n & 1){
            ans = Mul(ans, a, m);
        }
        a = Mul(a, a, m);
        n >>= 1;
    }
    return ans;
}

bool Miller_Rabbin(LL n){
    if (n==2)return true;
    if (n<2||!(n&1))return false;
    int t=0;
    LL a,x,y,u=n-1;
    while((u&1)==0) t++,u>>=1;
    for(int i=0;i<case;i++){
        a=rand()%(n-1)+1;
        x=Quick_mod(a,u,n);
        for(int j=0;jif (y==1&&x!=1&&x!=n-1)
                return false;
            ///其中用到定理,如果对模n存在1的非平凡平方根,则n是合数。
            ///如果一个数x满足方程x^2≡1 (mod n),但x不等于对模n来说1的两个‘平凡’平方根:1或-1,则x是对模n来说1的非平凡平方根
            x=y;
        }
        if (x!=1)///根据费马小定理,若n是素数,有a^(n-1)≡1(mod n).因此n不可能是素数
            return false;
    }
    return true;
}

long long factor[1000];//质因数分解结果(刚返回时是无序的)
int tol;//质因数的个数。数组小标从0开始

long long gcd(long long a,long long b){
    if(a==0)return 1;//???????
    if(a<0) return gcd(-a,b);
    while(b){
        long long t=a%b;
        a=b;
        b=t;
    }
    return a;
}

long long Pollard_rho(long long x,long long c){
    long long i=1,k=2;
    long long x0=rand()%x;
    long long y=x0;
    while(1){
        i++;
        x0=(Mul(x0,x0,x)+c)%x;
        long long d=gcd(y-x0,x);
        if(d!=1&&d!=x) return d;
        if(y==x0) return x;
        if(i==k){y=x0;k+=k;}
    }
}
//对n进行素因子分解
void findfac(long long n){
    if(Miller_Rabbin(n))//素数{
        factor[tol++]=n;
        return;
    }
    long long p=n;
    while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1);
    findfac(p);
    findfac(n/p);
}

int main(){
    int t;
    LL n;
    srand(time(NULL));
    scanf("%d",&t);
    while(t--){
        scanf("%lld",&n);
        if(Miller_Rabbin(n))
            cout<<"Prime"<else{
            tol = 0;
            findfac(n);
            LL minn = 99999999999999999;
            for(int i=0; icout<return 0;
}

求n的因数个数(是因数,不是素因数)

对n进行素因数分解,得到p1^a1+p2^a2+……+pi^ai
则其因数个数为:(a1+1)*(a2+1)*……+(ai+1)

常见的素数的应用就是这些,欢迎大家留言补充

你可能感兴趣的:(ACM-数论)