质数相关算法及优化

筛法求质数时间空间双优化

思路来源:MoreWindows的博客,原文地址:http://blog.csdn.net/morewindows/article/details/7347459

时间优化

在利用质数筛除合数的时候,我们发现大量合数会被重复筛去(以合数10为例,作为2的倍数会被筛一次,作为5的倍数又会被筛一次),因此思路就是尽量减少对合数的重复访问与筛选。

算法步骤如下:

首先,每个合数都有一个最小质因数,例如18的最小质因数是2,630的最小质因数是7。这样一来,可以令每个合数仅作为其最小质因数的倍数时被筛去,即可完全避免合数的重复访问:

for (int i=2; i<=n; ++i) {        //求n以内的质数
    if (!f[i]) 
        prime[primeN++]=i; //找到质数,加入质数表
    for (int j=0; (j<primeN) && (i*prime[j]<=n); ++j) {  //从小到大枚举质数
        f[i * prime[j]] = 1;
        if (i % prime[j] == 0) 
            break;
    } 
}

这里解释一下break语句。
设合数 p = i * prime[j+1]。当 i 为 prime[j] 的倍数时,p 必有质因数 prime[j]。由于 prime[j] 必定比 prime[j+1]小,p 之后必定会作为prime[j]的倍数被筛掉,因此不必重复访问,直接break。
举个例子,当 i = 6, prime[j] = 2,prime[j+1] = 3时,12被筛去,如果不break,18会作为3的倍数被筛掉。但18也是2的倍数,当 i = 9时18必定会被筛去。

空间优化

如果使用一个bool数组来标记每个数是否为质数,这意味着对于每个数的判断都需要一个字节,即8位的空间。实际上,8位二进制数完全可以为8个数进行标记。这样一来空间消耗可减少到原来的1/8。同理,一个int型变量(4字节,32位)即可为32个数作标记。

筛法求质数最终版:

int GetPrime(int n, int* prime) {//返回n以内质数的个数
    int primeN = 0;
    unsigned int f[n/32+1];
    unsigned int one = 1;
    memset(f, 0, sizeof(f));  
    // 1 << 31 会超出有符号int的范围,因此全部替换为无符号int
    for (unsigned int i = 2; i <= n; ++i) {
        if (!(f[i/32] & (one << (i % 32)))) 
            prime[primeN++] = i;         
        for (unsigned int j = 0; (j < primeN) && (i * prime[j] <= n); ++j) {
            unsigned int t = i * prime[j];
            f[t / 32] |= (one << (t % 32));
            if (t == 0) break;
        }  
    }
    return primeN;
}



组合数算法优化

求组合数 C n m = n ! m ! ( n − m ) ! C_n^m = \frac{n!}{m!(n-m)!} Cnm=m!(nm)!n! 。其中 n , m ≤ 1 0 5 n, m \leq 10^5 n,m105,最终结果对 1 0 9 + 7 10^9+7 109+7取模。

显然,如果按原公式计算,不仅超时,而且突破了int范围。

实际上,由上面的公式可知计算时会有大量的重复计算,且分子分母会约掉很多的数。因此,可考虑将所有的阶乘分解,然后分子分母上下相消,达到优化的目的。

更进一步的思路:我们可以将阶乘结果视为质因数的乘积,然后只需知道阶乘结果中包含的质因数各有多少个即可,再结合取模运算规则(a * b) % p = (a % p * b % p) % p,一边累乘一边mod,就能避免超过int范围。

代码
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define MOD 1000000007
int t, n, m, sum, total, prime[10000];
unsigned long long ans;
int Calc(int r,int p); //计算 r! 中包含了多少个质因数p
int GetPrime(int n, int* prime)
int main() {
    sum = GetPrime(100000, prime); 
    //求出100000以内的所有质数,保存在数组p中
    scanf("%d", &t);
    while (t--) {
        ans = 1;
        scanf("%d%d", &n, &m);
        if (m>n) {  //交换两数,保证n>=m
            n ^= m;
            m ^= n;  
            n ^= m;
        }
        for (int i=0; i<sum; i++) {
            total = Calc(n,prime[i]) - Calc(n-m,prime[i]) - Calc(m,prime[i]);
            for (int j=0; j<total; j++) ans=ans*prime[i] % MOD;
        }
        printf("%lld\n", ans % MOD); 
    }
    return 0;
}

int Calc(int r,int p) {
    if (r < p) return 0;
    return Calc(r / p, p) + r / p;
}
函数Calc详解

r < p r <p r<p 时,显然 r ! r! r! 无法分解出因数 p p p

r ≥ p r \geq p rp 时, r ! r! r! 中每隔 p p p 个数就会出现一个 p p p 的倍数。但当 r ≥ p 2 r \geq p^2 rp2时,在这些 p p p 的倍数中,每隔 p 2 p^2 p2 个数会出现一个 p 2 p^2 p2 的倍数。一旦出现一个 p 2 p^2 p2 的倍数,质因数的数量要加2。同理, 当 r ≥ p 3 r \geq p^3 rp3 时每隔 p 3 p^3 p3 个数会出现一个 p 3 p^3 p3 的倍数,同时质因数的数量要加3……

由于 p p p 的倍数每隔 p p p 出现一次,其数量为 r / p r/p r/p(向下取整)。 p 2 p^2 p2 的倍数每隔 p 2 p^2 p2 出现一次,其数量为 r / p 2 r/p^2 r/p2(向下取整)。 p 3 p^3 p3 的倍数每隔 p 3 p^3 p3 出现一次,其数量为 r / p 3 r/p^3 r/p3(向下取整),以此类推。则质因数 p p p 的数量为 r / p + r / p 2 + r / p 3 + . . . r/p + r/p^2 + r/p^3 + ... r/p+r/p2+r/p3+...

在统计 p p p 的倍数的数量时, p 2 p^2 p2 的倍数也被算到里面了,因此在计算质因数 p p p 的数量时, r / p 2 r/p^2 r/p2 项不需要乘以2。同理,在统计 p p p 的倍数以及 p 2 p^2 p2 的倍数的数量时, p 3 p^3 p3 的倍数均被算到里面了,因此在计算质因数 p p p 的数量时, r / p 3 r/p^3 r/p3 项也不需要乘以3,以此类推。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值