lucas
1.证明
2.用处
C(n, m) % p = C(n / p, m / p) * C(n%p, m%p) % p
大组合数求模
对于这种问题C(n, m) % p, n, m, p 都比较大的, 就要用卢卡斯定理了, 但是这里又分了两种情况, 一种是n, m 在1e6以内的, 并且case比较多的, 那么我们每次取调用卢卡斯肯定会T的, 所以我们要做的就是朴素的算就是了, 也就是直接调用组合数公式, 这个时候我们只要先预处理好1e6以内的带mod阶乘就行啦…. 类似于离线求
1 const int maxn = 1e5+5; 2 ll f[maxn]; 3 void init() { // 预处理阶乘 4 f[0] = f[1] = 1; 5 for (int i = 2 ; i <= 100001 ; i ++) { 6 f[i] = i % mod * f[i-1] % mod; 7 } 8 9 } 10 ll qpow(ll x,ll y,ll mod) { // 快速幂 11 ll res = 1; 12 while(y){ 13 if(y & 1) res = res * x % mod; 14 x = x * x % mod; 15 y >>= 1; 16 } 17 return res; 18 } 19 ll inv(ll n,ll mod) { // 求逆元 20 return qpow(n,mod-2,mod); 21 }
但是如果n, m 非常大了, 也就是达到1e18的那种, 那么我们就要上卢卡斯定理了, 并且T一般会比较少(<100).
同时如果mod数也非常的大, 那么在快速幂的同时还要套上快速乘才行, 防止爆….. 在线求.
1 //求a * b % mod; 由于这道题的mod都很大,所以乘法都容易爆,故需要快速乘. 2 ll mut_mod(ll a,ll b,ll mod) 3 { 4 ll res = 0; 5 while(b){ 6 if(b&1) res = (res + a) % mod; 7 a = (a + a) % mod; 8 b >>= 1; 9 } 10 return res; 11 } 12 //扩展GCD. 13 ll ex_gcd(ll a,ll b,ll &x,ll &y) 14 { 15 if(!b){ 16 x = 1; y = 0; 17 return a; 18 } 19 ll r = ex_gcd(b,a%b,x,y); 20 ll tmp = x; 21 x = y ; y = tmp - a/b*y; 22 return r; 23 } 24 //快速幂用于求逆元,虽然ex_gcd也行,但是我不会用. xx. 25 ll qpow(ll x,ll y,ll mod) 26 { 27 ll res = 1; 28 while(y){ 29 if(y & 1) res = mut_mod(res,x,mod) % mod; 30 x = mut_mod(x,x,mod) % mod; 31 y >>= 1; 32 } 33 return res; 34 } 35 //n % mod 的逆元 36 ll inv(ll n,ll mod) 37 { 38 return qpow(n,mod-2,mod); 39 } 40 //求n! % mod 41 ll fac(ll n,ll mod) 42 { 43 ll res = 1; 44 for(int i=2;i<=n;i++) res = res * i % mod; 45 return res; 46 } 47 //C(n, m) % mod 48 ll Comb(ll n,ll m, ll mod) 49 { 50 if(m>n) return 0; 51 return fac(n,mod) * inv(fac(m,mod),mod) % mod * inv(fac(n-m,mod),mod) % mod; 52 } 53 //卢卡斯定理,求C(n,m)%mod,且n,m,mod都很大. 54 ll Lucas(ll n, ll m,ll mod){ 55 return m ? Lucas(n/mod, m/mod, mod) * Comb(n%mod, m%mod,mod) % mod : 1; 56 } 57 // 要求mod为质数.
此部分来自https://blog.csdn.net/Anxdada/article/details/79506800
3.题目
hdu5446
#include<cstdio> typedef long long LL; const int N = 100000 + 5; LL mul(LL a, LL b, LL p){//快速乘,计算a*b%p LL ret = 0; while(b){ if(b & 1) ret = (ret + a) % p; a = (a + a) % p; b >>= 1; } return ret; } LL fact(int n, LL p){//n的阶乘求余p LL ret = 1; for (int i = 1; i <= n ; i ++) ret = ret * i % p ; return ret ; } void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){ if (!b) {d = a, x = 1, y = 0;} else{ ex_gcd(b, a % b, y, x, d); y -= x * (a / b); } } LL inv(LL t, LL p){//如果不存在,返回-1 LL d, x, y; ex_gcd(t, p, x, y, d); return d == 1 ? (x % p + p) % p : -1; } LL comb(int n, int m, LL p){//C(n, m) % p if (m < 0 || m > n) return 0; return fact(n, p) * inv(fact(m, p), p) % p * inv(fact(n-m, p), p) % p; } LL Lucas(LL n, LL m, int p){ return m ? Lucas(n/p, m/p, p) * comb(n%p, m%p, p) % p : 1; } LL china(int n, LL *a, LL *m){//中国剩余定理 LL M = 1, ret = 0; for(int i = 0; i < n; i ++) M *= m[i]; for(int i = 0; i < n; i ++){ LL w = M / m[i]; //ret = (ret + w * inv(w, m[i]) * a[i]) % M;//这句写了会WA,用下面那句 ret = (ret + mul(w * inv(w, m[i]), a[i], M)) % M; //因为这里直接乘会爆long long ,所以我用快速乘(unsigned long long也是爆掉,除非用高精度) } return (ret + M) % M; } int main(){ int T, k; LL n, m, p[15], r[15]; scanf("%d", &T); while(T--){ scanf("%I64d%I64d%d", &n, &m, &k); for(int i = 0; i < k; i ++){ scanf("%I64d", &p[i]); r[i] = Lucas(n, m, p[i]); } printf("%I64d\n", china(k, r, p)); } }