[组合数]求组合数的几种方法总结

求C(n,m)%mod的方法总结

1.当n,m都很小的时候可以利用杨辉三角直接求。 
C(n,m)=C(n-1,m)+C(n-1,m-1);

2.利用乘法逆元。 
乘法逆元:(a/b)%mod=a*(b^(mod-2)) mod为素数。 
逆元可以利用扩展欧几里德或欧拉函数求得:

 1).扩展欧几里德:b*x+p*y=1 有解,x就是所求

 2).费马小定理:b^(p-1)=1(mod p),故b*b^(p-2)=1(mod p),所以x=b^(p-2)(

(1) n!/(m!*(n-m)! =x%p ,先对算出n!、m!、(n-m)!对p取模的余数,就转换为a/b=x%p;因为p为素数,所以等价于bx+py=a;然后用扩展的欧几里得定理算出 bx’+py’=1的解,x=x’*a,就得到了最终的x的值,即C(m,n)%p得值。

(2).逆元 其实如果mod是素数 则b的逆元其实就是b^(mod-2) 
也就是 (m!(n-m)!)的逆元为 (m!(n-m)!)p-2 ;

int inv(int a) {  
    //return fpow(a, MOD-2, MOD);  
    return a == 1 ? 1 : (long long)(MOD - MOD / a) * inv(MOD % a) % MOD;  
}  
LL C(LL n,LL m)  
{  
    if(m < 0)return 0;  
    if(n < m)return 0;  
    if(m > n-m) m = n-m;  

    LL up = 1, down = 1;  
    for(LL i = 0 ; i < m ; i ++){  
        up = up * (n-i) % MOD;  
        down = down * (i+1) % MOD;  
    }  
    return up * inv(down) % MOD;  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.当n和m比较大,mod是素数且比较小的时候(10^5左右),通过Lucas定理计算

Lucas定理:A、B是非负整数,p是质数。A B写成p进制:A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0]。 
则组合数C(A,B)与C(a[n],b[n])C(a[n-1],b[n-1])…*C(a[0],b[0]) mod p同余 
即:Lucas(n,m,p)=C(n%p,m%p)*Lucas(n/p,m/p,p)

#include
//#include
using namespace std;
typedef long long ll;
int quick_power_mod(int a,int b,int m){//pow(a,b)%m
    int result = 1;
    int base = a;
    while(b>0){
         if(b & 1==1){
            result = (result*base) % m;
         }
         base = (base*base) %m;
         b>>=1;
    }
    return result;
}
//计算组合数取模
ll comp(ll a, ll b, int p) {//composite num C(a,b)%p
    if(a < b)   return 0;
    if(a == b)  return 1;
    if(b > a - b)   b = a - b;

    int ans = 1, ca = 1, cb = 1;
    for(ll i = 0; i < b; ++i) {
        ca = (ca * (a - i))%p;
        cb = (cb * (b - i))%p;
    }
    ans = (ca*quick_power_mod(cb, p - 2, p)) % p;
    return ans;
}
ll lucas(ll n, ll m, ll p) {
     ll ans = 1;

     while(n&&m&&ans) {
        ans = (ans*comp(n%p, m%p, p)) % p;//also can be recusive
        n /= p;
        m /= p;
     }
     return ans;
}
int main(){
    ll m,n;
    while(cin>>n>>m){
        cout<10007)<return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

C(n % mod, m % mod) % mod; 如果太大还是利用上面的逆元来处理。

半预处理 
由于Lucas定理保证了阶乘的数均小于p,所以可以讲所有的阶乘先预处理,优化C(n,m) 
mod的要求:p<10^6,且为素数 
有效范围:1<=n,m<=10^9

//半预处理  
const ll MAXN = 100000;  
ll fac[MAXN+100];  
void init(int mod)  
{  
    fac[0] = 1;  
    for (int i=1; i<mod; i++){  
        fac[i] = fac[i-1] * i % mod;  
    }  
}  

//半预处理逆元求C(n,m)%mod  
ll C(ll n, ll m)  
{  
    if ( m>n ) return 0;  
    return fac[n] * (GetInverse(fac[m]*fac[n-m], mod)) % mod;  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

4.还有一种就是分解质因子,这个比较麻烦。 
http://www.mamicode.com/info-detail-621758.html

你可能感兴趣的:(acm,知识点)