总结组合数的几种求法(模板)


way1.打表C(n,m)

原理:

  • 杨辉三角
  • \(\sum_{i=m}^{n}C_{i}^{m}=C_{n+1}^{m+1}\)
  • 即下图中绿色方框的数等于红色方框内数的总和:

空间:

  • O(nm)

时间:

  • 预处理O(nm)
  • 查询O(1)
for(int i=0;i<=n;i++){
	c[i][0]=c[i][i]=1;
	for(int j=1;j<i;j++)
		c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}

way2. 阶乘无模

原理:

  • 组合数基本公式
  • \(C_n^m=\frac{n!}{m!(n-m)!}\)

空间:

  • O(n)

时间:

  • 预处理O(1)
  • 查询O(n)

但这有个缺点就是涉及除法,无法直接取模

所以我们就要引入乘法逆元:


way3.乘法逆元+快速幂+阶乘

原理:

  • 费马小定理:
  • \(a^{p-1} \equiv 1 (mod\, p)\) (p是素数)
  • 两边同除a得:
  • \(a^{p-2} \equiv \frac{1}{a} (mod\, p)\)
  • 我们知道mod p意义下\(\frac{1}{a}\)就是a的逆元
  • 因此可得mod p意义下a的逆元\(inv(a)=a^{p-2}\)
  • 而这个\(a^{p-2}\)又可以用快速幂一只log求得

空间:

  • O(n)

时间:

  • 预处理O(n)
  • 查询O(log p)
int pow(int x,int y){ //快速幂
	int res=1;
	x%=p;
	for(;b;b>>=1,a=a*a%p) if(b&1) res=res*a%p;
    return res;
}
int inv(int x,int p){ //求逆元
	return pow(x,p-2);
}
fac[0]=1;
for(int i=1;i<=n;i++)
	fac[i]=fac[i-1]*i; //预处理出阶乘
return ((fac[n]*inv(fac[m],p))%p*inv(fac[n-m],p))%p;

way4.Lucas定理

Lucas定理是用于处理组合数取模的定理

通常用于解决阶乘无法解决的问题。

原理:

  • \(Lucas(n,m,p)\: mod\: p=Lucas(\frac{n}{p},\frac{m}{p},p)*C_{n\: mod\: p}^{m\: mod\: p}\: mod\: p\)
  • \(Lucas(n,m,p)\: mod\: p=C_n^m\: mod \: p\)

洛谷P3807为例

#define int long long
int t,n,m,p,f[100005];
int pow(int x,int y,int p){ //快速幂
	x%=p;
	int ans=1;
	for(int i=y;i;i>>=1,x=x*x%p) if(i&1) ans=ans*x%p;
	return ans;
}
int C(int n,int m,int p){ //求组合数
	if(m>n) return 0;
	return ((f[n]*pow(f[m],p-2,p))%p*pow(f[n-m],p-2,p)%p);
}
int lucas(int n,int m,int p){ //Lucas定理
	if(!m) return 1;
	return C(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
}
signed main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d",&n,&m,&p);
		f[0]=1;
		for(int i=1;i<=p;i++)
			f[i]=(f[i-1]*i)%p;
		printf("%lld\n",lucas(n+m,m,p));
	}
}
posted @ 2019-07-18 10:05  think_twice  阅读(2943)  评论(2编辑  收藏  举报