引入
我们都知道,求解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++