卢卡斯定理
当 m , n m,n m,n比较大时达到了 l o n g l o n g long~~long long long时,需要使用卢卡斯定理
时间复杂度 O ( l o g p n ∗ l o g 2 p ) O(log_pn*log_2p) O(logpn∗log2p)
取模数p为质数
A , B A,B A,B是非负整数, p p p是质数。 A , B A,B A,B写成 p p p进制: A = a [ n ] a [ n − 1 ] … a [ 0 ] A=a[n]a[n-1]…a[0] A=a[n]a[n−1]…a[0], B = b [ n ] b [ n − 1 ] … b [ 0 ] B=b[n]b[n-1]…b[0] B=b[n]b[n−1]…b[0]
则组合数 C A B C_A^B CAB与 ( C a [ n ] b [ n ] ∗ C a [ n − 1 ] b [ n − 1 ] … ∗ C a [ 0 ] b [ 0 ] ) m o d p (C_{a[n]}^{b[n]}*C_{a[n-1]}^{b[n-1]}…*C_{a[0]}^{b[0]})~mod~~p (Ca[n]b[n]∗Ca[n−1]b[n−1]…∗Ca[0]b[0]) mod p同余
即: L u c a s ( n , m , p ) = C ( n % p , m % p ) ∗ L u c a s ( n / p , m / p , p ) Lucas(n,m,p) = C(n\%p,m\%p)*Lucas(n/p,m/p,p) Lucas(n,m,p)=C(n%p,m%p)∗Lucas(n/p,m/p,p)
ll qkp(ll x, ll n, ll p) {
ll ans = 1;
x %= p;
while (n) {
if (n & 1) ans = ans * x % p;
x = x * x % p;
n >>= 1;
}
return ans;
}
ll inv(ll x, ll p) { //求逆元
return qkp(x, p - 2, p);
}
ll cal(ll n, ll m, ll p) {
if (m > n) return 0;
ll u = 1, d = 1;
for (int i = n - m + 1; i <= n; i++) u = u * i % p;
for (int i = 1; i <= m; i++) d = d * i % p;
return u * inv(d, p) % p;
}
ll lucas(ll n, ll m, ll p) {
if (!m) return 1;
return cal(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
如果题目给的 p ~p~ p 较小,可能使算法的时间复杂度退化到 ( l o g n ) 2 (logn)^2 (logn)2
观察可知卢卡斯定理每次计算的组合数的 n , m ~n,m~ n,m 都小于 p ~p~ p ,那么可以预处理保存 p ~p~ p 的范围内的阶乘对 p ~p~ p 取模的结果,此时的时间复杂度为 O ( l o g n ) O(logn) O(logn)
int num;
vector<int> prime;
bool is_prime[maxn];
ll fac[maxn][1300];
unordered_map<int, int> mp;
void getPrime() { //欧拉筛求素数
int m = sqrt(maxn + 0.5);
memset(is_prime, 1, sizeof is_prime);
is_prime[0] = is_prime[1] = 0;
for (int i = 2; i < maxn; i++) {
if (is_prime[i]) prime.push_back(i);
for (int j = 0; j < prime.size() && 1LL * i * prime[j] < maxn; j++) {
is_prime[i * prime[j]] = 0;
if (i % prime[j] == 0) break;
}
}
mp.clear(), num = 0;
for (auto i: prime) mp[i] = ++num;
}
void init() {
for (auto p: prime) {
int j = mp[p];
fac[0][j] = 1;
for (int i = 1; i < maxn; i++)
fac[i][j] = fac[i - 1][j] * i % p;
}
}
ll qkp(ll x, ll n, ll p) {
ll ans = 1;
x %= p;
while (n) {
if (n & 1) ans = ans * x % p;
x = x * x % p;
n >>= 1;
}
return ans;
}
ll inv(ll x, ll p) { //求逆元
return qkp(x, p - 2, p);
}
ll cal(ll n, ll m, ll p) {
if (m > n) return 0;
return fac[n][mp[p]] * inv(fac[m][mp[p]], p) % p * inv(fac[n - m][mp[p]], p) % p;
}
ll lucas(ll n, ll m, ll p) {
if (!m) return 1;
return cal(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
取模数p为合数
求 C n m m o d p C_n^m~mod~~p Cnm mod p,其中 p p p为合数
原理
将 p p p质因数分解为 p = p 1 a 1 ∗ p 2 a 2 . . . p k a k p=p_1^{a_1}*p_2^{a_2}...p_k^{a_k} p=p1a1∗p2a2...pkak,问题就变成了求同余方程组:
{ x ≡ C n m ( m o d p 1 a 1 ) x ≡ C n m ( m o d p 2 a 2 ) . . . . . . x ≡ C n m ( m o d p n a n ) \left\{ \begin{array}{rcl}x\equiv C_n^m(mod~~p_1^{a_1}) \\ x\equiv C_n^m(mod~~p_2^{a_2}) \\ ...... \\ x\equiv C_n^m(mod~~p_n^{a_n})\end{array}\right. ⎩⎪⎪⎨⎪⎪⎧x≡Cnm(mod p1a1)x≡Cnm(mod p2a2)......x≡Cnm(mod pnan)
因为 p 1 , p 2 , . . . p n p_1,p_2,...p_n p1,p2,...pn互质,最后使用中国剩余定理合并答案
问题就变成了求 C n m m o d p k C_n^m~mod~~p^k Cnm mod pk
思路
根据组合数的定义,上述问题转化为: n ! m ! ( n − m ) ! m o d p k \frac{n!}{m!(n-m)!}~mod~~p^k m!(n−m)!n! mod pk
因为求逆元的充要条件为 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1,因此不能对 m ! , ( n − m ) ! m!,(n-m)! m!,(n−m)!求逆元,考虑不能求逆元时就是 x ! x! x!中含有质因数 p p p,考虑除掉这个质因子,则问题变为:
n ! p x m ! p y ( n − m ) ! p z p x − y − z m o d p k \frac{\frac{n!}{p^x}}{\frac{m!}{p^y}\frac{(n-m)!}{p^z}}p^{x-y-z}~mod~~p^k pym!pz(n−m)!pxn!px−y−z mod pk,其中 p x p^x px代表 n ! n! n!中质因子 p p p的个数
设 f ( n ) = n ! p x f(n)=\frac{n!}{p^x} f(n)=pxn!,那么问题又转化为 f ( n ) f ( m ) f ( n − m ) p x − y − z m o d p k \frac{f(n)}{f(m)f(n-m)}p^{x-y-z}~mod~~p^k f(m)f(n−m)f(n)px−y−z mod pk
只要能求出 f ( n ) f(n) f(n)就大功告成
对 n ! n! n!进行变形,将其中 p p p的倍数和非倍数分开看,可以得到 n ! = ( p ∗ 2 p ∗ 3 p . . . t p ) ( 1 ∗ . . . ) n!=(p*2p*3p...tp)(1*...) n!=(p∗2p∗3p...tp)(1∗...)
又因为 1 − n 1-n 1−n中有 ⌊ n ! p ⌋ \lfloor\frac{n!}{p}\rfloor ⌊pn!⌋个 p p p的倍数,那么又能得到 p ⌊ n ! p ⌋ ( 1 ∗ 2... ∗ t ) ( 1 ∗ . . . ) p^{\lfloor\frac{n!}{p}\rfloor}(1*2...*t)(1*...) p⌊pn!⌋(1∗2...∗t)(1∗...)
即 p ⌊ n ! p ⌋ ( ⌊ n ! p ⌋ ) ! ( ∏ i = 1 , i % p ! = 0 n i ) ① p^{\lfloor\frac{n!}{p}\rfloor}(\lfloor\frac{n!}{p}\rfloor)!(\prod_{i=1,i\%p!=0}^ni)~~① p⌊pn!⌋(⌊pn!⌋)!(∏i=1,i%p!=0ni) ①
其中 ∏ i = 1 , i % p ! = 0 n i \prod_{i=1,i\%p!=0}^ni ∏i=1,i%p!=0ni这个式子是有循环节的,例如 15 ! m o d 7 15!~mod~~7 15! mod 7,那么后面的式子为:
( 1 ∗ 2 ∗ 3 ∗ 4 ∗ 5 ∗ 6 ) ∗ ( 8 ∗ 9 ∗ 10 ∗ 11 ∗ 12 ∗ 13 ) ∗ 15 = ( 1 ∗ 2 ∗ 3 ∗ 4 ∗ 5 ∗ 6 ) ∗ ( 8 % 7 ∗ 9 % 7 ∗ 10 % 7 ∗ 10 % 7 ∗ 11 % 7 ∗ 12 % 7 ∗ 13 % 7 ) ∗ 15 = ( 1 ∗ 2 ∗ 3 ∗ 4 ∗ 5 ∗ 6 ) 2 ∗ 15 (1*2*3*4*5*6)*(8*9*10*11*12*13)*15 \\ =(1*2*3*4*5*6)*(8\%7*9\%7*10\%7*10\%7*11\%7*12\%7*13\%7)*15 \\ =(1*2*3*4*5*6)^2*15 (1∗2∗3∗4∗5∗6)∗(8∗9∗10∗11∗12∗13)∗15=(1∗2∗3∗4∗5∗6)∗(8%7∗9%7∗10%7∗10%7∗11%7∗12%7∗13%7)∗15=(1∗2∗3∗4∗5∗6)2∗15
因此 ① ① ①式又可以化为 p ⌊ n ! p ⌋ ( ⌊ n ! p ⌋ ) ! ( ∏ i = 1 , i % p ! = 0 p k i ) ⌊ n p k ⌋ ( ∏ i = ⌊ n p k ⌋ p k , i % p ! = 0 n i ) p^{\lfloor\frac{n!}{p}\rfloor}(\lfloor\frac{n!}{p}\rfloor)!(\prod_{i=1,i\%p!=0}^{p^k}i)^{\lfloor\frac{n}{p^k}\rfloor}(\prod_{i=\lfloor\frac{n}{p^k}\rfloor p^k,i\%p!=0}^{n}i) p⌊pn!⌋(⌊pn!⌋)!(∏i=1,i%p!=0pki)⌊pkn⌋(∏i=⌊pkn⌋pk,i%p!=0ni)
因为 p ⌊ n ! p ⌋ p^{\lfloor\frac{n!}{p}\rfloor} p⌊pn!⌋是要提取出来化为 p x − y − z p^{x-y-z} px−y−z,但是 ( ⌊ n ! p ⌋ ) ! (\lfloor\frac{n!}{p}\rfloor)! (⌊pn!⌋)!还可能包含 p p p,那么需要这样定义 f ( n ) f(n) f(n):
f ( n ) = f ( ⌊ n p ⌋ ) ( ∏ i = 1 , i % p ! = 0 p k i ) ⌊ n p k ⌋ ( ∏ i = ⌊ n p k ⌋ p k , i % p ! = 0 n i ) f(n)=f(\lfloor\frac{n}{p}\rfloor)(\prod_{i=1,i\%p!=0}^{p^k}i)^{\lfloor\frac{n}{p^k}\rfloor}(\prod_{i=\lfloor\frac{n}{p^k}\rfloor p^k,i\%p!=0}^{n}i) f(n)=f(⌊pn⌋)(∏i=1,i%p!=0pki)⌊pkn⌋(∏i=⌊pkn⌋pk,i%p!=0ni),递归边界为 f ( 0 ) = 1 f(0)=1 f(0)=1
最后还需要求 p x − y − z p^{x-y-z} px−y−z,设 g ( n ) = ⌊ n p ⌋ + g ( ⌊ n p ⌋ ) g(n)=\lfloor\frac{n}{p}\rfloor+g(\lfloor\frac{n}{p}\rfloor) g(n)=⌊pn⌋+g(⌊pn⌋),递归边界为 i f ( n < p ) g ( n ) = 0 if(n<p) ~~~g(n)=0 if(n<p) g(n)=0
这样分别求出 g ( n ) , g ( m ) , g ( n − m ) g(n),g(m),g(n-m) g(n),g(m),g(n−m),则 p x − y − z = p g ( n ) − g ( m ) − g ( n − m ) p^{x-y-z}=p^{g(n)-g(m)-g(n-m)} px−y−z=pg(n)−g(m)−g(n−m)
综上,总的公式为: f ( n ) f ( m ) f ( n − m ) p g ( n ) − g ( m ) − g ( n − m ) m o d p k \frac{f(n)}{f(m)f(n-m)}p^{g(n)-g(m)-g(n-m)}~mod~~p^k f(m)f(n−m)f(n)pg(n)−g(m)−g(n−m) mod pk
inline void exgcd(ll a, ll b, ll &x, ll &y) {
if (!b) {
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= a / b * x;
}
inline ll inv(ll a, ll p) {
ll x, y;
exgcd(a, p, x, y);
return (x % p + p) % p;
}
inline ll mul(ll x, ll n, ll p) {
ll ans = 0;
x %= p, n %= p;
while (n) {
if (n & 1) ans = (ans + x) % p;
x = (x + x) % p;
n >>= 1;
}
return ans;
}
inline ll qkp(ll x, ll n, ll p) {
ll ans = 1;
x %= p;
while (n) {
if (n & 1) ans = ans * x % p;
x = x * x % p;
n >>= 1;
}
return ans;
}
inline ll f(ll n, ll p, ll mod) {
if (!n) return 1;
ll tmp = 1, res = 1;
for (ll i = 1; i <= mod; i++)
if (i % p) {
tmp = tmp * i % mod;
}
tmp = qkp(tmp, n / mod, mod);
for (ll i = mod * (n / mod); i <= n; i++)
if (i % p) {
res = res * (i % mod) % mod;
}
return f(n / p, p, mod) * tmp % mod * res % mod;
}
inline ll g(ll n, ll p) {
if (n < p) return 0;
return g(n / p, p) + n / p;
}
inline ll cal(ll n, ll m, ll p, ll mod) {
ll fn = f(n, p, mod), fm = inv(f(m, p, mod), mod), fnm = inv(f(n - m, p, mod), mod);
ll res = qkp(p, g(n, p) - g(m, p) - g(n - m, p), mod);
return fn * fm % mod * fnm % mod * res % mod;
}
ll A[maxn], B[maxn];
inline ll exlucas(ll n, ll m, ll p) {
ll res = p, cnt = 0;
for (ll i = 2; i * i <= p && res > 1; i++)
if (res % i == 0) {
ll pk = 1;
while (res % i == 0) pk *= i, res /= i;
A[++cnt] = pk, B[cnt] = cal(n, m, i, pk);
}
if (res != 1) A[++cnt] = res, B[cnt] = cal(n, m, res, res);
ll ans = 0;
for (ll i = 1; i <= cnt; i++) {
ll M = p / A[i], M1 = inv(M, A[i]);
ans = (ans + B[i] * M % p * M1 % p) % p;
}
return ans;
}