1. 程式人生 > >組合數取模

組合數取模

ios AS names 局限性 代碼 lap div 沒有 AC

組合數取模問題為求$C_{n}^m % p$的值。根據$n$,$m$,$p$取值不同,方法不同。
在此之前我們先看些前置技能:

同余定理:$a≡b(mod\ m)$
性質:
1.傳遞性:若$a≡b(mod\ m)$,$b≡c(mod\ m)$,則$a≡c(mod\ m)$;
2.同余式相加:若$a≡b(mod\ m)$,$c≡d(mod\ m)$,則$a±c≡b±d(mod\ m)$;
3.同余式相乘:若$a≡b(mod\ m)$,$c≡d(mod\ m)$,則$ac≡bd(mod\ m)$。

逆元(數論倒數):對於正整數$a$和$m$,如果有$ax≡1(mod\ m)$,那麽把這個同余方程中$x$的最小正整數解稱為$a mod\ m$的逆元。
為什麽叫數論倒數呢,因為沒有$mod$操作,那麽$x$就相當於$a$的倒數,有$mod$操作時效果上和倒數一樣。同時我們把$a$的逆元寫做:$inv(a)$。
逆元求解一般用擴展歐幾裏得算法,如果$m$為素數,那麽還可以根據費馬小定理得到逆元為$a^{m-2} mod\ m$。

擴展歐幾裏得算法和費馬小定理求解逆元具有局限性,兩種算法都要求a和m互素。

1.擴展歐幾裏得:$a*x + b*y = 1$
解$x$就是$a$關於$b$的逆元,解$y$就是$b$關於$a$的逆元
$a*x \% b + b*y \% b = 1 \% b$
$a*x \% b = 1 \% b$
$a*x = 1 (mod\ b)$

2.費馬小定理:$a\hat{}(p-1)≡1 (mod\ p)$
兩邊同時除以$a$,$a\hat{}(p-2)≡inv(a)(mod\ p)$;即$inv(a)≡a^(p-2)(mod\ p)$

Lucas定理:

$n=n_kp^k+n_{k-1}p^{k-1}+...+n_1p+n_0$

$m=m_kp^k+m_{k-1}p^{k-1}+...+m_1p+m_0$

得到:$C_n^m=\prod\limits_{i=0}^{k}C_{ni}^{mi}(mod\ p)$

具體代碼中實現:$Lucas(n,m,p)=C(n\%p,m\%p)*Lucas(n/p,m/p,p)\%p $

經典例題1:http://acm.fzu.edu.cn/problem.php?pid=2020

$1 <= m <= n <= 10^9, m <= 10^4, m < p < 10^9, p是素數$,$m$比較小,$n$,$p$比較大的情況。

該題cin輸入會比較快,懷疑是scanf讀入%lld比較耗時,或者是OJ問題。

解決方案1:Lucas定理

技術分享圖片
 1 #include <cstdio>
 2 #include <iostream>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 typedef long long LL;
 7 
 8 LL fast_power(LL a,LL b,LL p){
 9     LL ans=1;
10     a%=p;
11     while(b){
12         if(b&1) ans=(ans*a)%p;
13         a=(a*a)%p;
14         b>>=1;
15     }
16     return ans;
17 }
18 
19 LL C(LL a,LL b,LL p){
20     if(b>a) return 0;
21     if(a==b) return 1;
22     LL ans1=1,ans2=1;
23     for(LL i=1;i<=b;i++){
24         ans1=ans1*(a-i+1)%p;
25         ans2=ans2*i%p;
26     }
27     return ans1*fast_power(ans2,p-2,p)%p;
28 }
29 
30 LL Lucas(LL a,LL b,LL p){
31     if(b==0) return 1;
32     return C(a%p,b%p,p)*Lucas(a/p,b/p,p)%p;
33 }
34 
35 int main(){
36     int t;
37     cin>>t;
38     while(t--){
39         LL n,m,p;
40         cin>>n>>m>>p;
41         cout<<Lucas(n,m,p)<<endl;
42     }
43     return 0;
44 }
View Code

解決方案2:暴力+逆元

技術分享圖片
 1 #include <cstdio>
 2 #include <iostream>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 typedef long long LL;
 7 
 8 LL fast_power(LL x,LL n,LL mod){
 9     LL ans=1;
10     x%=mod;
11     while(n){
12         if(n&1) ans=(ans*x)%mod;
13         n>>=1;
14         x=(x*x)%mod;
15     }
16     return ans;
17 }
18 
19 int main(){
20     int t;
21     cin>>t;
22     while(t--){
23         LL n,m,p;
24         cin>>n>>m>>p;
25         LL ans1=n,ans2=1;
26         for(LL i=1;i<m;i++){
27             ans1=ans1*(n-i)%p;
28             ans2=ans2*i%p;
29         }
30         ans2=ans2*m%p;
31         cout<<ans1*fast_power(ans2,p-2,p)%p<<endl;
32     }
33     return 0;
34 }
View Code

經典例題2:http://codeforces.com/gym/101775/problem/A

$1?≤?N?≤?10^9,3?≤?K?≤?10^5,mod=1000000007$

組合數取模