组合数的几种求法

12 篇文章 0 订阅
10 篇文章 0 订阅


前言

在很多算法的应用中,组合数常常作为一个重要的组成部分,想要计算出组合数也有许多算法,那么,该如何在合适的地方使用合适的算法呢?

一、组合数的定义

公式: C a b C_a^b Cab = = = a ! / ( b ! ) ∗ ( b − a ) ! a!/(b!)*(b-a)! a!/(b!)(ba)!
时间复杂度: O ( b ) O(b) O(b)
说明:一般在 a a a较大且 b b b较小时使用,代码较为简单易懂,可以搭配逆元使用。
代码(逆元为费马小定理):

ll C(ll a,ll b){
	ll up = 1,down = 1;
	for(int i = a,j = 1;j<=b;--i,++j){
		up = up * i % p;
		down = down * j % p;
	}
	return up * qmi(down,p-2);
}

二、杨辉三角

公式: C a b C_a^b Cab = = = C a − 1 b C_{a-1}^b Ca1b + + + C a − 1 b − 1 C_{a-1}^{b-1} Ca1b1
时间复杂度: O ( a 2 ) O(a^2) O(a2)
说明:前提为 a a a比较小,可以在询问条目很大时用作离线打表节约时间。
代码:

ll C(ll a,ll b){
	for(int i=0;i<=a;++i){
		for(int j=0;j<=b&&j<=i;++j){
			if(!j) c[i][j] = 1;
			else  c[i][j] = c[i-1][j] + c[i-1][j-1];
		}
	}
	return c[a][b];
}

三、Lucas定理

公式: l u c a s ( a , b ) lucas(a,b) lucas(a,b) = = = l u c a s ( a / p , b / p ) lucas(a/p,b/p) lucas(a/p,b/p) ∗ * C a % p b % p C_{a\%p}^{b\%p} Ca%pb%p
时间复杂度: O ( l o g p n ∗ p ) O(log_p^n*p) O(logpnp)
说明:用于以较快的速度计算很大的组合数,通常要配合阶乘打表食用。
代码:

ll fact[N],infact[N];
void get_fact(){
	fact[0] = infact[0] = 1;  
	for(int i=1;i<N;++i){
		fact[i] = fact[i-1]*i%p;
		infact[i] = infact[i-1]*qmi(i,p-2)%p;
	}
}

ll C(ll a,ll b){
	if(a<b) return 0;
	return fact[a]*infact[a-b]*infact[b]%p;
}

ll lucas(ll a,ll b){
	if(a<p&&b<p) return C(a,b);
	return lucas(a/p,b/p)*C(a%p,b%p)%p; 
}

四、分解质因数

方法:获取结果的所有质因数的次数,并以快速幂求解
时间复杂度: O ( l o g a l o g a ) O(logaloga) O(logaloga)
说明:用于以较快的速度计算很大的组合数,模数不为质数也没有关系,但代码量和前几种相比较多。
代码:

int prime[M],cnt;
bool st[M];
int n,p;
void init(int n){
	for(int i=2;i<=n;++i){
		if(!st[i]) prime[cnt++] = i;
		for(int j=0;prime[j]*i<=n&&j<cnt;j++){
			st[prime[j]*i] = true;
			if(i%prime[j]==0) break;
		}
	}
}
ll qmi(ll a,ll b){
	ll res = 1;
	while(b){
		if(b&1) res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return res;
}
ll get(ll b,ll a){
    ll s = 0;
    while(b) s += b/a , b = b/a;
	return s; 
}
ll C(ll a,ll b){
	if(a<b) return 0;
	ll res = 1;
	for(int i=0;i<cnt;++i){
		ll pr = prime[i];
		//a!/b!/(a-b)!
		ll s = get(a,pr) - get(b,pr) - get(a-b,pr);
		res = res * qmi(pr,s) % p;
	}
	return res;
}

总结

本文仅仅讲了求组合数的各种方法,在算法中具体要用到哪种,或者要以怎样的形式使用还需另加思考。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

某六十九岁合法萝莉

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

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

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

打赏作者

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

抵扣说明:

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

余额充值