Lucas定理

引入

我们都知道,求解C(n,m)可以利用公式C(n,m)=C(n-1,m)+C(n-1,m-1)暴力打表求解。

那么问题来了,求解C(n,m)%p(n和m是非负整数,p是素数),n和m都很大而p很小(注意:n和m很大是指相对很大,至少你存不下,p很小是指相对很小,一般为≤109左右),或者n,m不大但大于p。

显然这样用阶乘解决不了。

那么,我们就引入一个求解方法:Lucas定理


结论

首先,介绍Lucas定理的两个结论

结论1:

  Lucas(n,m,p)=cm(n%p,m%p)×Lucas(n/p,m/p,p);

  Lucas(x,0,p)=1;

  其中:cm(a,b)=a!×(b!×(a-b)!)(p-2)%p;

         =(a!/(a-b)!)×(b!)(p-2)%p;

     你会发现这其实就是C(n,m)的逆元。

结论2:

  把n写成p进制a[n]×a[n-1]×a[n-2]×…×a[0],把m写成p进制b[n]×b[n-1]×b[n-2]×…×b[0],则C(n,m)≡C(a[n],b[n])×C(a[n-1],b[n-1])×C(a[n-2],b[n-2])×…×C(a[0],b[0])(mod p)。


Lucas定理的实现代码

int cm(int n,int m)
{
	if(n<m)return 0;//注意n%p可能小于m%p,则直接返回0即可
	if(2*m>n)m=n-m;
	long long s1=1,s2=1;
	for(int i=0;i<m;i++)
	{
		s1=s1*(n-i)%p;
		s2=s2*(i+1)%p;
	}
	return s1*qpow(s2,p-2)%p;//求s2的p-2次方,不是用于求逆元,注意qpow是快速幂,不是内置函数
}
int lucas(int n,int m)
{
	if(!m)return 1;
	return cm(n%p,m%p)*lucas(n/p,m/p)%p;
}

那么,可能会出现一个问题,如果需要大量的运用Lucas定理,建议打表。

void pre_work()
{
	jc[0]=1;
	for(int i=1;i<=n;i++)
		jc[i]=jc[i-1]*i%p;
	for(int i=0;i<=n;i++)
		qsm[i]=qpow(jc[i],p-2)%p;
}
int cm(int x,int y)
{
	return jc[x]*qsm[y]*qsm[x-y];
}
int lucas(int x,int y)
{
	if(!y)return 1;
	return cm(x%p,y%p)*lucas(x/p,y/p)%p;
}

rp++

posted @ 2019-07-01 10:35  HEOI-动动  阅读(235)  评论(0编辑  收藏  举报