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