数学基础:素数筛法、分解素因数

首先强调:0、1不是素数。

当求较大范围内符合特殊要求的素数时,若预处理时间太长,可以打表。先把答案跑出来,放入表中直接读取即可。洛谷P1218

判断一个数是不是素数,可以用O(sqrt(n))的复杂度来实现

bool judge(int x)
{
    int y=sqrt(x)+1;
    for(int i=2;i<=y;i++){
        if(x%i==0){
            return false;
        }
    }
    return true;
}

当求一个范围内的素数个数或是素数具体的值时,一个一个判断就速度太慢。这种情况采用素数筛法进行处理。首先是普通筛法(埃氏筛法),预先进行处理,记录下来素数的值,便于之后查询。

void init(int n)
{
    for(int i=0;i<=n;i++){
        mark[i]=false;
    }
    cnt=0;
    for(int i=2;i<=n;i++){
        if(mark[i])  continue;
        prime[++cnt]=i;
        for(int j=i*i;j<=n;j+=i){         //注意int*int可能会溢出
            mark[j]=true;
        }
     }
}

但是普通筛法会对合数进行重复处理,对此进一步优化有欧式筛法。欧式筛法复杂度为O(n),主要优越之处在于避免了对合数的重复处理,从而加快了处理时间。因此不仅可以统计一定范围内的素数,而且可以较为快速的求出素数的数目。

void init()
{
    for(int i=0;i<=n;i++){
        mark[i]=false;
    }
    cnt=0;
    for(int i=2;i<=n;i++){
        if(!mark[i]){
            prime[++cnt]=i;
        }
        for(int j=1;j<=cnt;j++){
            if(i*prime[j]>n)    break;
            mark[i*prime[j]]=true;
            //下面的判断是算法核心,每次都使prime[j]为当前合数的最小因子,如此才能避免对合数的重复处理
            if(i%prime[j]==0)    break;
        }
    }
}

对于复杂度要求更高的题目来说,欧式筛法也不能满足。MEISSEL-LEHMER算法,复杂度为O(N^(2/3))。但是太复杂了,一般也用不到,有需要的可以自己了解......


分解素因数,即将一个数分解为a=(p1^e1)*(p2^e2)*(p3^e3)......思路是预先处理出sqrt(n)范围内的素数,然后遍历已有素数。对当前素数一直除,直到a%prime[i]!=0,同时进行计数;对每个素数进行这样的处理,直到a=1;若到最后a!=1,则有超出sqrt(n)素数范围的一个大素数。这样就完成了素因数的分解。

但是对于n!,情况要发生变化。因为n!太大了,算出来n!再求素因数不合适。因此对于n!的素因数分解,有以下方法:

for(int i=0;i<primeSize;i++){
    cnt[i]=0;
}
//对n!分解素因数,遍历每个素数
for(int i=0;i<primeSize;i++){
    int t=n;    //用t作为临时变量
    //依次计算t/num[i]^k,累加其值,直到t变为0
    while(t){
        cnt[i]+=t/num[i];
        t=t/num[i];
    }
}

这样处理的考虑是:n!包含了1到n的数的乘积。对于一个作为素因数的数p,1到n中可以提供一个p的数,应当有n/p个。那么,可以提供p^2的数,应当有n/p^2个。考虑到这个过程中有重复,所以直接累加即可。

求组合数时,C(n,r)=n!/(r!*(n-r)!),如果直接计算阶乘会出问题,可以采用以下方法

#include<stdio.h>
#include<string.h>
typedef long long ll;
ll gcd(ll a,ll b){
    if(b==0) return a;
    return gcd(b,a%b);
}
ll factorial(ll n,ll m){
    if(n==0||m==0) return 1;
    m=n-m>m?m:n-m;
    ll a=1,b=n-m+1,ans=1,cnt=1;
    while(cnt<=m){
        ll tmp=gcd(b,a);
        ans=ans*(b/tmp)/(a/tmp);
        a++;b++;
        cnt++;
    }
    return ans;
}
int main(){
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
        if(n==0&&m==0) break;
        printf("%lld\n",factorial(n,m));
    }
    return 0;
}

 

posted @ 2020-10-09 11:39  太山多桢  阅读(455)  评论(0)    收藏  举报