转载https://www.cnblogs.com/fzl194/p/9095177.html
组合数取模方法总结(Lucas定理介绍)
1.当n,m都很小的时候可以利用杨辉三角直接求。
C(n,m)=C(n-1,m)+C(n-1,m-1);
const int maxn = 1e5 + 10; ll fac[maxn];//阶乘打表 void init(ll p)//此处的p应该小于1e5,这样Lucas定理才适用 { fac[0] = 1; for(int i = 1; i <= p; i++) fac[i] = fac[i - 1] * i % p; } ll pow(ll a, ll b, ll m) { ll ans = 1; a %= m; while(b) { if(b & 1) ans = (ans % m) * (a % m) % m; b /= 2; a = (a % m) * (a % m) % m; } ans %= m; return ans; } ll niyuan(ll x, ll p)//x关于p的逆元,p为素数 { return pow(x, p - 2, p); } ll C(ll n, ll m, ll p)//组合数C(n, m) % p { if(m > n) return 0; return fac[n] * niyuan(fac[m] * fac[n - m], p) % p; } ll Lucas(ll n, ll m, ll p) { if(m == 0) return 1; return C(n % p, m % p, p) * Lucas(n / p, m / p, p) % p; }
2、n和m较大,但是p为素数的时候
Lucas定理是用来求 c(n,m) mod p,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
由于p是素数,有费马小定理可知,m! * (n - m)! 关于p的逆元就是m! * (n - m)!的p-2次方。
p较小的时候预处理出1-p内所有阶乘%p的值,然后用快速幂求出逆元,就可以求出解。p较大的时候只能逐项求出分母和分子模上p的值,然后通过快速幂求逆元求解。
n! C(n,r) = -------------------- r!∗(n−r)!
ll pow(ll a, ll b, ll m) { ll ans = 1; a %= m; while(b) { if(b & 1)ans = (ans % m) * (a % m) % m; b /= 2; a = (a % m) * (a % m) % m; } ans %= m; return ans; } ll niyuan(ll x, ll p)//x关于p的逆元,p为素数 { return pow(x, p - 2, p); } ll C(ll n, ll m, ll p)//组合数C(n, m) % p { if(m > n) return 0; ll up = 1, down = 1;//分子分母; for(int i = n - m + 1; i <= n; i++) up = up * i % p; for(int i = 1; i <= m; i++) down = down * i % p; return up * niyuan(down, p) % p; } ll Lucas(ll n, ll m, ll p) { if(m == 0) return 1; return C(n % p, m % p, p) * Lucas(n / p, m / p, p) % p; }