求组合数

基本公式:

C n m = A n m A m m , C n m = n ! m ! ( n − m ) ! C_n^m = \frac{A_n^m}{A_m^m}, C_n^m = \frac{n!}{m!(n - m)!} Cnm=AmmAnm,Cnm=m!(nm)!n!

定义法:

利用 C n m = n ! m ! ( n − m ) ! C_n^m = \frac{n!}{m!(n - m)!} Cnm=m!(nm)!n!来求,通常用于多次求在某一模下的组合数设inv[x]为x!的逆元则
C n m = n ! m ! ( n − m ) ! = n ! ∗ i n v [ n ! ] ∗ i n v [ ( n − m ) ! ] % m o d C_n^m = \frac{n!}{m!(n - m)!} = n! * inv[n!] * inv[(n - m)!] \% mod Cnm=m!(nm)!n!=n!inv[n!]inv[(nm)!]%mod
由于 i n v [ n ! ] = i n v [ ( n + 1 ) ! ] ∗ ( n + 1 ) % m o d inv[n!] = inv[(n + 1)!] * (n + 1) \% mod inv[n!]=inv[(n+1)!](n+1)%mod
所以只需要求出最大数的阶乘的逆元然后就可以O(n)递推剩下的逆元了,复杂度为O(n)

const ll maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
ll fec[maxn],inv[maxn];
ll qpow(ll a,ll b){
    ll res = 1;
    while(b){
        if(b & 1)
            res = (res * a) % mod;
        b >>= 1;
        a = (a * a) % mod;
    }
    return res;
}
ll C(ll n,ll m){//求组合数
    if(m == 0 || n == m)return 1;
    else return fec[n]*inv[m] % mod * inv[n - m] % mod;
}
int main(){
    IO;
    fec[0] = 1;
    for(int i = 1; i <= 2001; i++)
        fec[i] = fec[i - 1] * i % mod;//fec存阶乘
    inv[2001] = qpow(fec[2001],mod - 2);
    for(int i = 2000; i >= 1; i--)
        inv[i] = inv[i + 1] * (i + 1) % mod;//递推求逆元
    return 0;
}

lucas定理求逆元

用于求a和b特别大的组合数(比如0 < a,b < 1e18),这样的话递推就不管用了
lucas定理: C n m ≡ C n % p m % p ∗ C n / p m / p ( m o d    p ) C_n^m \equiv C_{n \% p}^{m \% p}*C_{n / p}^{m / p}(\mod p) CnmCn%pm%pCn/pm/p(modp)其中P为质数(通常比较小)
根据lucas定理就可以比较快的求出大的组合数

ll qpow(ll a,ll b){
    ll res = 1;
    while(b){
        if(b & 1)
            res = (res * a) % p;
        b >>= 1;
        a = (a * a) % p;
    }
    return res;
}
ll C(ll a,ll b){
    ll ans = 1;
    for(ll i = 1,j = a; i <= b; i++,j--){
        ans = (ans * j) % p;
        ans = ans * qpow(i,p - 2) % p;
    }
    return ans;
}
ll lucas(ll a,ll b){
    if(a < p && b < p)return C(a,b);
    else return C(a % p,b % p) * lucas(a / p,b / p) % p;
}
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页