组合数求解总结

组合数

意图计算 C a b C^b_a Cab的值,此处列举三种常用方法。

简单组合计数

a , b a,b a,b很小(小于等于3000左右),采用递推的方法,根据公式 C a b = C a − 1 b − 1 + C a − 1 b C^b_a=C^{b-1}_{a-1}+ C^{b}_{a-1} Cab=Ca1b1+Ca1b,用二维数组 C [ i ] [ j ] C[i][j] C[i][j]表示 C i j C^j_i Cij

#include<iostream>
#define N 2010
using namespace std;
typedef long long ll;
ll C[N][N];
void init(){//预处理打表2000以内的组合数。
	for(int i = 0;i<=2000;++i){
		for(int j = 0;j<=i;++j){
			if(!j) C[i][j] = 1;
			else C[i][j] = C[i-1][j]+C[i-1][j-1];
		}
	}
}
int main(){
	int n,a,b;
	init();//打表
	cin >> n;
	while(n--){
		cin >> a >> b;
		cout << C[a][b]<<"\n";//查询
	}
	return 0;
}	

阶乘逆元求解

a , b ≤ 100000 a,b\le100000 a,b100000时,已经不能采用简单组合计数的方法,时间复杂度过高。这里可以利用公式 C a b = a ! ( a − b ) ! × b ! C^b_a = \frac{a!}{(a-b)!\times b!} Cab=(ab)!×b!a!,将1~n的阶乘以及其逆元计算得出即可。另外,应用这类方法时,通常都会进行取模运算,此处以mod = 1e9+7为例。
代码如下:

#include<iostream>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
ll fact[100010],infact[100010];
ll qpow(ll a,ll p){
	ll ans = 1;
	while(p){
		if(p&1) ans = (ans*a)%mod;
		p>>=1;
		a*=a;
		a%=mod;
	}
	return ans%mod;
}
int main(){
	int n,a,b;
	cin >>n;
	fact[0] = infact[0] = 1;
	for(int i = 1;i<=100000;++i){
		fact[i] = fact[i-1]*i%mod;
		infact[i] = infact[i-1]*qpow(i,mod-2)%mod;
		//逆元i乘以i-1的阶乘逆元,结果是i的阶乘逆元
		//也可以用i的阶乘逆元乘以i,反推得到i-1的阶乘逆元
	}
	while(n--){
		cin >> a >> b;
		//三项直接相乘可能会long long 溢出
		ll ans = (fact[a]*infact[a-b]%mod)*infact[b]%mod;
		cout << ans <<"\n";
	}
	return 0;
}

Lucas定理

1 ≤ a , b ≤ 1 0 18 , 模 数 1 ≤ p ≤ 1 0 5 1\le a,b\le 10^{18},模数1\le p\le10^5 1a,b1018,1p105时打表已经不可取了,此处应采用卢卡斯定理 C a b ≡ C a % p b % p × C a / p b / p ( m o d    p ) C^b_a\equiv C^{b\%p}_{a\%p}\times C^{b/p}_{a/p}(\mod p) CabCa%pb%p×Ca/pb/p(modp),当a,b都小于p,而p的大小在 1 0 5 10^5 105,可以通过定义递推得到 C a b C^b_a Cab,若有比p大,则继续调用Lucas定理求解。

#include<iostream>
using namespace std;
typedef long long ll;
ll fact[100010],infact[100010];
ll mod;
ll qpow(ll a,ll p){
	ll ans = 1;
	while(p){
		if(p&1) ans = (ans*a)%mod;
		p>>=1;
		a*=a;
		a%=mod;
	}
	return ans%mod;
}
ll c(int a,int b){//a,b<=mod,规模小
	ll res = 1;
	for(int i = 1,j=a;i<=b;++i,--j){
		res = res * j % mod;
		res = res * qpow(i,mod-2) % mod;
	}
	return res;
}
ll Lucas(ll a,ll b){//卢卡斯定理
	if(a<mod&&b<mod) return c(a,b);
	return c(a%mod.b%mod)*Lucas(a/mod,b/mod)%mod;
}
int main(){
	ll a,b,n;
	cin >>n;
	while(n--){
		cin >> a >> b >> mod;
		cout << Lucas(a,b);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

registor11

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

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

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

打赏作者

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

抵扣说明:

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

余额充值