Lucas定理
Lucas定理适用于组合数C(n,m)mod p 时,n和m较大,但是p为素数的时候。
原理就不写了。
其直接应用为:C(n,m)%p=C(n/p,m/p)*C(n%p,m%p)%p,即Lucas(n,m)%p=Lucas(n/p,m/p)*C(n%p,m%p)%p
求上式的时候,Lucas递归出口为m=0时返回1
求C(n%p, m%p)%p的时候,此处写成C(n, m)%p(p是素数,n和m均小于p)
C(n, m)%p = n! / (m ! * (n - m )!) % p = n! * mod_inverse[m! * (n - m)!, p] % p(mod_inverse是指 ...的逆元)
由于p是素数,有费马小定理可知,m! * (n - m)! 关于p的逆元就是m! * (n - m)!的p-2次方。
(求逆元详见乘法逆元)
给出有费马小定理与Lucas定理求解组合数中n,m较大且mod一个数的代码:
(n和m较小的时候可用打表的方法)
1 ll f[N];//f[n] = n! 2 void init(int p) 3 { 4 f[0]=1; 5 for(int i=1;i<=p;i++) 6 f[i]=f[i-1]*i%p; 7 } 8 ll pow_mod(ll x,ll y,int mod) 9 { 10 ll res=1; 11 while(y) 12 { 13 if(y&1) 14 res=res*x%mod; 15 x=x*x%mod; 16 y>>=1; 17 } 18 return res; 19 } 20 ll Lucas(ll n,ll k,int p)//C (n, k) % p 21 { 22 ll res=1; 23 while (n&&k) 24 { 25 ll nn=n%p,kk=k%p; 26 if(nn<kk) 27 return 0; 28 res=res*f[nn]*pow_mod(f[kk]*f[nn-kk]%p,p-2,p)%p;//inv (f[kk]) = f[kk] ^ (p - 2) % p 29 n/=p; 30 k/=p; 31 } 32 return res; 33 } 34 int main(void) 35 { 36 init(p); 37 printf("%lld ",Lucas(n,m,p)); 38 return 0; 39 }
看一个例题: #10228. 「一本通 6.6 例 3」组合 原题链接:https://loj.ac/problem/10228
具体题目不写了,这个题 1<=n,m<=109 ,m<=p<=109 ,很明显,n,m,p都太大了,打表会溢出,但是可以用递归来实现。
上面递归也说了,我们适应于本题,Lucas(n,m,p)%p=Lucas(n/p,m/p,p,p)*C(n%p,m%p,p)%p,(n,m是组合数,p是取模数,对应传值)。
Lucas(n/p,m/p,p,p)是递归式,当m==0时,Lucas(x,0,p)=1,为递归出口。
而C(n%p,m%p,p)%p可以应用费马小定理和乘法逆元求解。带上快速幂。
代码如下:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 typedef long long ll; 5 ll pow_mod(ll x,ll y,ll mod)//快速幂 6 { 7 ll ans=1; 8 while(y) 9 { 10 if(y&1) 11 ans=ans*x%mod; 12 x=x*x%mod; 13 y>>=1; 14 } 15 return ans; 16 } 17 ll C(ll n,ll m,ll p)//求递归式中C(n%p,m%p)%p 18 { 19 if(m>n) 20 return 0; 21 ll ans=1,a,b; 22 for(ll i=1;i<=m;i++)//循环求阶乘 23 { 24 a=(n+i-m)%p; 25 b=i%p; 26 ans=ans*(a*pow_mod(b,p-2,p)%p)%p;//费马小定理求逆元 27 } 28 return ans; 29 } 30 ll lucas(ll n,ll m,ll p)//Lucas定理的递归 31 { 32 if(m==0) 33 return 1; 34 return C(n%p,m%p,p)*lucas(n/p,m/p,p)%p; 35 } 36 int main() 37 { 38 int t; 39 ll n,m,p; 40 cin>>t; 41 while(t--) 42 { 43 scanf("%lld%lld%lld",&n,&m,&p); 44 ll ans; 45 ans=lucas(n,m,p); 46 printf("%lld ",ans); 47 } 48 return 0; 49 }