求组合数(最强优化)

简单优化(没有模运算)

当m < n-m 时,把n - m 改为 m,使用下方算法,简单优化
C n m = C n n − m C_{n}^{m}=C_{n}^{n-m} Cnm=Cnnm
代码如下:

#include<cstdio>
using namespace std;
long long C(int n, int m){
	if (m<n-m)	m=n-m;
	long long ans=1;
	for (int i=m+1;i<=n;i++) ans *= i;
	for (int i=1;i<=n-m;i++) ans /= i;
	return ans;
}
int main(){
	printf("%lld\n",C(n,m));
	return 0;
}

进阶优化(逆元 - 组合数取模)

问题:求 C n m C_{n}^{m} Cnm%p(经典p=1e9+7)

C n m C_{n}^{m} Cnm运算中,取模性质对除数不适用,需要将“除法”转换为“乘法”,才能借助才能借助取模的性质在不爆long long的情况下计算组合数。这时候就需要用到“逆元”!

逆元:对于a和p(a和p互素),若a*b%p≡1,则称b为a%p的逆元。

那这个逆元有什么用呢?试想一下求( a b \frac{a}{b} ba )%p,如果你知道b%p的逆元是c,那么就可以转变成( a b \frac{a}{b} ba )%p = a*c%p = (a%p)(c%p)%p

那怎么求逆元呢?这时候就要引入强大的费马小定理!

费马小定理:对于a和素数p,满足$ a^{p-1} $ %p≡1

接着因为$ a^{p-1} $ = a p − 2 a^{p-2} ap2 ∗a,所以有 a p − 2 a^{p-2} ap2∗a%p≡1!对比逆元的定义可得, a p − 2 a^{p-2} ap2是a的逆元!

所以问题就转换成求解 a p − 2 a^{p-2} ap2,即变成求快速幂的问题了(当然这需要满足p为素数)。

快速幂请参考博主的另一篇文章快速幂算法

现在总结一下求解 C n m C_{n}^{m} Cnm%p的步骤:

  1. 通过循环,预先算好所有小于max_number的阶乘( %p )的结果,存到fac[max_number]里 ( fac[i] = i ! %p )
  2. 求m!%p的逆元( 即求fac[m]的逆元 ):根据费马小定理,x%p的逆元为 x p − 2 x^{p-2} xp2,因此通过快速幂,求解 f a c [ m ] p − 2 fac[m]^{p-2} fac[m]p2 %p,记为M
  3. 求(n-m)!%p的逆元:同理为求解 f a c [ n − m ] p − 2 fac[n-m]^{p-2} fac[nm]p2%p,记为NM
  4. C n m C_{n}^{m} Cnm%p = ( ( fac[n] * M)%p * NM)%p

用C++实现的代码,输入为n,m,p,要求0<=m<=n<=1e6(否则fac存不下),且gcd(p,n!)=1(即互素),输出为Cmn%p

long long fac[MAXN];
long long n,m,p;
//快速幂求x^n%mod 
long long pow_mod(long long x, long long n, long long mod) {
    long long res=1;
    while(n){
        if(n&1)  res=res*x%mod;
        x=x*x%mod;
        n>>=1;
    }
    return res;
}
int main() {
    while(~scanf("%lld %lld %lld", &n, &m, &p)) {        
        //预处理求fac,fac[i]=i!%p
        fac[0] = 1;
        for (int i = 1; i <= n; i++) {fac[i] = fac[i - 1] * i % p;}
        //组合数 = n!*(m!%p的逆元)*((n-m)!%p的逆元)%p 
        printf("%lld\n", fac[n] * pow_mod(fac[m], p - 2, p) % p * pow_mod(fac[n - m], p - 2, p) % p);
    }
}

参考链接——逆元 - 组合数取模

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶辰 .

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值