你好|

ASnown

园龄:2年6个月粉丝:13关注:15

求组合数的几种方法

引入

在做题时,经常会遇到需要计算从 n 个物品中选择 m 个的方案数的情况。
我们就需要用到计算组合数的公式:Cmn=n!(nm)!m!。这篇文章里为了方便,用 (nm) 代替 Cmn

方法

1.乘法逆元

最简单的方法莫过于用组合数公式直接求。

但在 21! 就已经爆 long long,所以通常是会有模数的。
如果有了模数,那么普通的除法肯定是会出大问题,就又要引入逆元

如果一个线性同余方程 ax1(modb) ,则 x 称为 amodb 的逆元,记作 a1

定义 factn=n!modp (p 为模数),infactn=(n!)1modp。那么我们就可以把原始公式转换成 (nm)=factninfactnminfactm

在这里,求乘法逆元可以用线性递推法 invi=(ppi)invpmodimodp

const int mod = ...;
int inv[],fact[],infact[];
void init(){
	fact[1]=infact[1]=inv[1]=1;
	for(int i=2;i<=...;i++){
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		fact[i]=fact[i-1]*i%mod;
		infact[i]=infact[i-1]*inv[i]%mod;
	}
}
int C(int n,int m){
	return (ll)fact[n]*infact[n-m]%mod*infact[m]%mod;
}
顺带一提

那么既然 20! 不爆 long long,在 n,m20 的情况下你也可以写暴力。

2.预处理

nm 都不是很大的情况下,如 n,m104,可以直接杨辉三角预处理

const int mod = ...;
int C[][];
void init(){
	C[0][0]=1;
	for(int i=1;i<=...;i++)
		for(int j=0;j<=i;j++)
			if(j)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
			else C[i][j]=1;
}
int C(int n,int m){
	return C[n][m];
}

3.Lucas定理

一般用于 n,m 很大,mod 比较小的情况。

Lucas定理的证明和公式均在这篇博客中有,可自行跳转阅读,此处不再赘述。

具体求组合数的方法可以根据 mod 的范围自行选择,我这里选择了乘法逆元法。

const int mod = ...;
int inv[],fact[],infact[];
void init(){
	fact[1]=infact[1]=inv[1]=1;
	for(int i=2;i<=...;i++){
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		fact[i]=fact[i-1]*i%mod;
		infact[i]=infact[i-1]*inv[i]%mod;
	}
}
int C(int n,int m){
	return (ll)fact[n]*infact[n-m]%mod*infact[m]%mod;
}
int Lucas(int n,int m){
	if(n<mod)return C(n,m);
	return C(n%mod,m%mod) * Lucas(n/mod,m/mod) %mod;
}

本文作者:ASnown

本文链接:https://www.cnblogs.com/As-Snow/p/17223075.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ASnown  阅读(108)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起