数学问题——组合数

这篇博客介绍了如何计算阶乘中质因子的个数以及组合数的计算方法。针对n!中质因子p的个数,提出了递归计算公式,并以10!中质因子2的个数为例进行详细解析。此外,还探讨了计算组合数的递推公式和避免溢出的优化技巧,包括适用于不同数据范围的计算策略。同时,提到了Lucas定理在大数模运算中的应用。
摘要由CSDN通过智能技术生成

1 求n!中有多少个质因子p

1.1 以10!中质因子2的个数为例

设n!中的质因子p的个数为f(n, p)

f(10, 2) = (10 / 2) + f(10/2, 2) = 5 + f(5, 2)
f(5, 2) = (5 / 2) + f(5/2, 2) = 2 + f(2, 2)

2! = 1*2 = 2^{1} *1!                  f(2, 2) = (2/2) + f(2/2, 2) = 1 + f(1, 2) = 1 + 0 = 1

综上,f(10, 2) = 5+2+1 = 8

1.2 结论

        n!中质因子p的个数,实际上等于1~n中p的倍数的个数 \frac{n}{p} 加上 \frac{n}{p} ! 中质因子p的个数

// 计算 n! 中有多少个质因子p
int cal(int n, int p){
    if(n<p) return 0;  // n<p时1~n中不可能有质因子p
    return n/p + cal(n/p, p);
}

2 组合数的计算

2.1 组合数的基本概念

        C_{n}^{m}是指从n个不同元素中选出m个元素的方案数(m<=n)

        C_{n}^{m} = \frac{n!}{m!(n-m)!)}

        C_{n}^{m} = C_{n}^{n-m}

        C_{n}^{0} = C_{n}^{n} = 1

2.2 计算C_{n}^{m}

2.2.1 通过递推公式计算

        C_{n}^{m}可以转换为下面两种选法的方案数之和:一是不选最后一个数,从前n-1个数中选m个数;二是选最后一个数,从前n-1个数中选m-1个数。于是得递推公式:C_{n}^{m} = C_{n-1}^{m} + C_{n-1}^{m-1}

        用long long型存储结果,该方法在n=67、m=33时开始溢出

        该方法的时间复杂度为O(n^2)

// 递归代码
long long res[67][67] = {0};  // 将已经计算过的结果保存在数组中,可以减少重复计算
long long  C(long long n, long long m){
    if(m==0 || m==n) return 1;
    if(res[n][m] != 0) return res[n][m];
    return res[n][m] = C(n-1, m) + C(n-1, m-1);  // 赋值给res[n][m]并返回
}
// 或者是把整张表都计算出来的递推代码
const int n = 60;
void calC(){
    for(int i=0;i<=n;i++){
        res[i][0] = res[i][i] = 1;  // 初始化边界
    }
    for(int i=2;i<=n;i++){
        for(int j=0;j<=i/2;j++){
            res[i][j] = res[i-1][j] + res[i-1][j-1];    // 递推计算C(i, j)
            res[i][i-j] = res[i][j];  // C(i, i-j) = C(i, j)
        }
    }
}

2.2.2 通过定义式的变形来计算

C_{n}^{m} = \frac{(n-m+1)*(n-m+2)*...*(n-m+m)}{1*2*...*m}=\frac{\frac{\frac{\frac{n-m+1}{1}*(n-m+2)}{2}*...}{...}*(n-m+m)}{m}

只要能保证每次除法都是整除,就能用这种边乘边除的方法避免连续乘法溢出问题。

\frac{(n-m+1)*(n-m+2)*...*(n-m+i)}{1*2*...*i}=C_{n-m+i}^{i} (1<=i<=m)

C_{n-m+i}^{i}是个整数,显然每次除法都是整除。

时间复杂度为O(m),在n=62、m=31时开始溢出

long long C(long long n,long long m){
    long long ans=1;
    for(long long i=1;i<=m;i++)
        ans = ans*(n-m+i)/i;    // 注意一定要先乘在除
    return ans;
}

2.3 计算C_{n}^{m} % p

 2.3.1 通过递推公式计算

适用场景:支持m<=n<=10^4,p<=10^9级别的数据范围

// 递归代码
int res[1010][1010] = {0};  // 将已经计算过的结果保存在数组中,可以减少重复计算
int C(int n, int m,int p){
    if(m==0 || m==n) return 1;
    if(res[n][m] != 0) return res[n][m];
    return res[n][m] = (C(n-1, m) + C(n-1, m-1)) % p;  // 赋值给res[n][m]并返回
}
// 或者是把整张表都计算出来的递推代码
const int n = 1001;
void calC(){
    for(int i=0;i<=n;i++){
        res[i][0] = res[i][i] = 1;  // 初始化边界
    }
    for(int i=2;i<=n;i++){
        for(int j=0;j<=i/2;j++){
            res[i][j] = res[i-1][j] + res[i-1][j-1] % p;    // 递推计算C(i, j)
            res[i][i-j] = res[i][j];  // C(i, i-j) = C(i, j)
        }
    }
}

2.3.2 通过质因子分解来计算

适用场景:支持m<=n<=10^6,p<=10^9级别的数据范围

// 使用素数筛选法获得素数表prime,注意表中最大素数不得小于n
int prime[MAXN];
// 计算C(n,m)%p
int C(int n,int m,int p){
    int ans = 1;
    for(int i=0;prime[i]<=n;i++){
        // 计算C(n, m)中prime[i]的指数c, cal(n, k)为n!中含质因子k的个数
        int c = cal(n, prime[i]) - cal(m, prime[i]) - cal(n-m, prime[i]);
        // 快速幂计算prime[i]^c%p
        ans = ans *BinaryPow(prime[i], c, p) % p;
    }
    return ans;
}

2.3.3 Lucas定理

p是素数的条件下,将m和n表示为p进制:

\left\{\begin{matrix} m = m_{k}p^{k} + m_{k-1}p^{k-1} + ... + m_{0}\\ n = n_{k}p^{k} + n_{k-1}p^{k-1} + ... + n_{0} \end{matrix}\right.

Lucas定理告诉我们下面等式成立:

C_{n}^{m} \equiv C_{n_{k}}^{m_{k}} * C_{n_{k-1}}^{m_{k-1}} *...*C_{n_{0}}^{m_{0}}(mod\, \, p))

这意味着将 C_{n}^{m}%p 分解为O(logn)级别个小组合数的乘积的模。

适用场景:p<=10^5且p为素数。支持m<=n<=10^18级别的数据范围

int Lucas(int n,int m){
    if(m==0) return 1;
    return C(n%p,m%p)*Lucas(n/p, m/p) % p;
}

        回忆进制转换部分,将10进制转换为p进制只需要不断除以p,然后对p取模即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值