Lucas定理(卢卡斯定理)

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\n",Lucas(n,m,p));
38     return 0;
39 }

 

看一个例题: #10228. 「一本通 6.6 例 3」组合    原题链接:https://loj.ac/problem/10228

具体题目不写了,这个题 1<=n,m<=109 ,m<=p<=10,很明显,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\n",ans);
47     }
48     return 0;
49 }

 

posted on 2020-05-05 10:34  轻描淡写ぃ  阅读(850)  评论(0编辑  收藏  举报

导航