关于【逆元】和【lucas定理】

来源:互联网 发布:python time 加减 编辑:程序博客网 时间:2024/06/05 00:24

原文链接:点击打开链接

这个Lucas定理是解决组合数的时候用的,当然是比较大的组合数了。比如C(1000000,50000)% mod,这个mod肯定是要取的,要不算出来真的是天文数字了。

对于一个组合数C(n,k),它等于 n! / ( k! * ( n - k)! ) 我们要求一个mod。但是我们知道的同余定理是在 + - * 这三个运算中使用的,对于除法我们不能轻易的使用同余定理。如果我们能把除数(分母)转化为一个乘法就好了,这个时候我们就用到了逆元的知识:


这就开始说逆元了:

定义:对于正整数,如果有,那么把这个同余方程中的最小正整数解叫做的逆元。

     

如果m是素数且GCD(a,mod)== 1,我们就直接可以用费马小定理求了。即求:a^(m-2)% mod。

用快速幂求即可。


如果还不明白逆元是个啥,我举个简单的例子来看看:

求:(24 / 3)% 5 我们可以直接观察得结果:3

但是这个只是个24,如果前面是一个很大很大的数的连乘longlong都存不下呢?我们肯定是一边乘一边求mod。在这里,我们把24对5求模,结果是4。这个4不能直接除以3再求模,一看肯定是错误的。这里我们要把这个4乘3的逆元再求模。根据刚刚说的,3的逆元为3^(5-2) = 27 (或者用扩展欧几里得exGCD(3,5,x,y)这样求出来的x就是3mod5的逆元)。然后按照刚刚说的,4 * 27 % 5 = 3 ,这就是结果了。

反正根据我的理解就是,由于除法不能使用同余定理,那么我们就把除以的这个数转化为乘法,然后用同余定理即可。



逆元如果知道了,我们继续说Lucas定理的使用。

先说一下定义:

Lucas 定理:A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]...a[0],B=b[n]b[n-1]...b[0]   这里的每一个数组元素表示其p进制的每一位。

则组合数C(A,B)与C(a[n],b[n])*C(a[n-1],b[n-1])*...*C(a[0],b[0])。也就是说,把大组合数问题变成了一个个的小组合数。(A,B小于mod)


对于每一个小组合数,我们继续刚才的说明:n! / ( k! * ( n - k)! ) ,我们要求k!*(n - k)!的逆元。套用上面逆元的求法,再看一下下面的模板,应该就不难理解了。


Lucas定理用递归的方法,代码:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. LL Lucas(LL n,LL k)     //Lucas定理递归   
  2. {  
  3.     if (k == 0)     //递归终止条件   
  4.         return 1;  
  5.     else  
  6.         return C(n % mod , k % mod) * Lucas(n / mod , k / mod) % mod;  
  7. }  


然后我们要求组合数,代码:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. LL C(LL n , LL k)       //费马小定理求逆元   
  2. {  
  3.     if (k > n)  
  4.         return 0;  
  5.     else  
  6.         return fac[n] * (quick(fac[k] * fac[n-k] % mod , mod - 2)) % mod;  
  7. }  


这里用到了快速幂,代码:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. LL quick_mod(LL n , LL m)       //求快速幂   
  2. {  
  3.     LL ans = 1;  
  4.     n %= mod;  
  5.     while (m)  
  6.     {  
  7.         if (m & 1)  
  8.             ans = ans * n % mod;  
  9.         n = n * n % mod;  
  10.         m >>= 1;  
  11.     }  
  12.     return ans;  
  13. }  


对于阶乘,我们可以先打一个表,运算就快很多:

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. void getfac()       //打一个阶乘表   
  2. {  
  3.     for (int i = 2 ; i <= 1000000 ; i++)  
  4.         fac[i] = fac[i-1] * i % mod;  
  5. }  




来一个大代码:(求大组合数对mod = 1000003求模)

[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. #include <cstdio>  
  2. #include <cstring>  
  3. #include <algorithm>  
  4. using namespace std;  
  5. #define CLR(a,b) memset(a,b,sizeof(a))  
  6. #define INF 0x3f3f3f3f  
  7. #define LL long long  
  8. LL mod = 1000003;  
  9. LL fac[1000000+11] = {1,1};  
  10. void getfac()       //打一个阶乘表   
  11. {  
  12.     for (int i = 2 ; i <= 1000000 ; i++)  
  13.         fac[i] = fac[i-1] * i % mod;  
  14. }  
  15. LL quick_mod(LL n , LL m)       //求快速幂   
  16. {  
  17.     LL ans = 1;  
  18.     n %= mod;  
  19.     while (m)  
  20.     {  
  21.         if (m & 1)  
  22.             ans = ans * n % mod;  
  23.         n = n * n % mod;  
  24.         m >>= 1;  
  25.     }  
  26.     return ans;  
  27. }  
  28. LL C(LL n , LL k)       //费马小定理求逆元   
  29. {  
  30.     if (k > n)  
  31.         return 0;  
  32.     else  
  33.         return fac[n] * (quick_mod(fac[k] * fac[n-k] % mod , mod - 2)) % mod;  
  34. }  
  35. LL Lucas(LL n,LL k)     //Lucas定理递归   
  36. {  
  37.     if (k == 0)     //递归终止条件   
  38.         return 1;  
  39.     else  
  40.         return C(n % mod , k % mod) * Lucas(n / mod , k / mod) % mod;  
  41. }  
  42. int main()  
  43. {  
  44.     getfac();  
  45.     LL n,k;  
  46.     int Case = 1;  
  47.     int u;  
  48.     scanf ("%d",&u);  
  49.     while (u--)  
  50.     {  
  51.         scanf ("%lld %lld",&n,&k);  
  52.         printf ("Case %d: %lld\n",Case++,Lucas(n,k));  
  53.     }  
  54.     return 0;  
  55. }  

0 0