扩展卢卡斯相对较为复杂,需要较多的前置芝士。
至于卢卡斯定理,那真的不重要。
卢卡斯( L u c a s Lucas Lucas)和扩展卢卡斯( e x L u c a s exLucas exLucas)都用于求解形如 C n m m o d p C_{n}^{m} \mod p Cnmmodp的答案。当 p p p是质数时,直接用卢卡斯定理就可以*过去。
如果 p p p不是质数,就得用今天的主角——扩展卢卡斯定理求解。
由于 p p p不是质数,那么我们考虑强行对其进行质因数分解:
p = ∏ i = 1 n p i a i p=\prod_{i=1}^{n}p_i^{a_i} p=i=1∏npiai
那么分解完后每一项 p i a i p_i^{a_i} piai之间两两互质,只要我们能得出每组 C n m m o d p i a i C_n^m \mod p_i^{a_i} Cnmmodpiai的答案,就可以用中国剩余定理合并得到最后的答案。
然后来考虑求解 C n m m o d p i a i C_n^m \mod p_i^{a_i} Cnmmodpiai,我们知道 C n m = n ! m ! × ( n − m ) ! C_n^{m}=\frac{n!}{m!\times (n-m)!} Cnm=m!×(n−m)!n!,那么我们也就是要求下面这个式子:
n ! m ! × ( n − m ) ! m o d p a \frac{n!}{m!\times (n-m)!}\mod p^{a} m!×(n−m)!n!modpa
那么显然我们要求出阶乘和阶乘的逆元。由于阶乘与模数当前不互质,所以极有可能不存在直接的逆元,需要我们对式子进行进一步的拆分。
我们从 n ! n! n!中把 p p p的倍数全部提出,原式就会变成这样一个式子:
n ! = p ⌊ n p ⌋ × ⌊ n p ⌋ ! × ∏ p ∤ i n i n!=p^{\lfloor\frac{n}{p}\rfloor}\times \lfloor\frac{n}{p}\rfloor!\times \prod_{p \nmid i}^{n}i n!=p⌊pn⌋×⌊pn⌋!×p∤i∏ni
式子中的 p ⌊ n p ⌋ p^{\lfloor\frac{n}{p}\rfloor} p⌊pn⌋可以直接快速幂求出, ⌊ n p ⌋ ! \lfloor \frac{n}{p} \rfloor ! ⌊pn⌋!考虑递归求解,唯一比较麻烦的是后面剩下的不能被 p p p整除的数。
仔细观察(去你丫的)可以发现,剩下这些项的乘积有循环节,长度小于 p a p^a pa。由于显然 x ≡ x + p a ( m o d p a ) x \equiv x+p^a \pmod {p^a} x≡x+pa(modpa),所以可以求出共有几个循环节,对第一个循环节暴力求一遍,然后用快速幂搞定。对于最后剩余的不能构成完整循环节的几项,依然是暴力求一遍搞定。
举个经典的例子: n = 19 n=19 n=19, p = 3 p=3 p=3, a = 2 a=2 a=2
n ! = 1 ∗ 2 ∗ 3 ∗ 4 ∗ 5 ∗ 6 ∗ 7 ∗ 8 ∗ 9 ∗ 10 ∗ 11 ∗ 12 ∗ 13 ∗ 14 ∗ 15 ∗ 16 ∗ 17 ∗ 18 ∗ 19 = ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) ∗ ( 10 ∗ 11 ∗ 13 ∗ 14 ∗ 16 ∗ 17 ) ∗ 19 ∗ 3 6 ∗ 6 ! ≡ ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) 2 ∗ 19 ∗ 3 6 ∗ 6 ! \begin{aligned} n! &= 1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19 \\ &=(1*2*4*5*7*8)*(10*11*13*14*16*17)*19*3^6*6! \\ & \equiv(1*2*4*5*7*8)^2*19*3^6*6! \end{aligned} n!=1∗2∗3∗4∗5∗6∗7∗8∗9∗10∗11∗12∗13∗14∗15∗16∗17∗18∗19=(1∗2∗4∗5∗7∗8)∗(10∗11∗13∗14∗16∗17)∗19∗36∗6!≡(1∗2∗4∗5∗7∗8)2∗19∗36∗6!
于是我们对括号内的求一遍,然后快速幂,接着暴力求未能构成循环节的 19 19 19,最后递归求解 6 ! 6! 6!。
由于 3 6 3^6 36次这一项比较特殊,因为存在这一项,所以才可能不存在逆元,所以求阶乘的时候我们不对其进行计算,而在求组合数的时候单独处理。也就是说,我们求的阶乘,实际上是去掉了所有因子 p p p后的阶乘。
这一部分的代码:
为了方便,我们会把 p a p^a pa当做参数传入到 f a c fac fac函数中,即代码中的 k k k。
ll fac(ll n, ll p, ll k) { //n! % k (k=p^a)
if(!n) return 1; //0! = 1
ll ans = 1;
for(int i = 2; i <= k; i++) { //处理循环节内的数字
if(i%p) ans = ans*i % k;
}
ans = qpow(ans, n/k, k); //所有循环节的乘积
for(int i = 2; i <= n%k; i++) { //剩下单独的项
if(i%p) ans = ans*i % k;
}
return ans*fac(n/p, p, k)%k; //递归求解
}
再回到求解组合数的过程。
上面说到,要在求组合数时单独处理形如 p x p^x px的项,那么具体的操作就是:我们用一个 c n t cnt cnt变量记录最后的式子中有多少个因子 p p p,那么 c n t cnt cnt就等于 n n n的阶乘中含有因子 p p p的数量减去 m m m和 n − m n-m n−m的阶乘中含有的 p p p的数量。
求法就是不断枚举范围内有多少个 p x p^x px的倍数(小学基本功)。
//记得开long long,n很大,直接把int炸飞
for(ll i = p; i <= n; i *= p) cnt += n/i;
for(ll i = p; i <= m; i *= p) cnt -= m/i;
for(ll i = p; i <= n-m; i *= p) cnt -= (n-m)/i;;
然后答案就是 n ! ∗ i n v ( m ! ) ∗ i n v ( ( n − m ) ! ) ∗ p c n t n!*inv(m!)*inv((n-m)!)*p^{cnt} n!∗inv(m!)∗inv((n−m)!)∗pcnt。
这一段的代码:
ll C(ll n, ll m, ll p, ll k){ //k = p^a
if(n < m) return 0;
ll a = fac(n,p,k), b = fac(m,p,k), c = fac(n-m,p,k);
ll cnt = 0;
for(ll i = p; i <= n; i *= p) cnt += n/i;
for(ll i = p; i <= m; i *= p) cnt -= m/i;
for(ll i = p; i <= n-m; i *= p) cnt -= (n-m)/i;
return a*inv(b, k)%k * inv(c, k)%k * qpow(p, cnt, k)%k;
}
接下来就到了正式的 e x L u c a s exLucas exLucas部分。
首先我们把给定的模数 p p p进行质因数分解。
然后对于每个 p i a i p_i^{a_i} piai,都求一遍组合数 C n m m o d p i a i C_n^m \mod p_i^{a_i} Cnmmodpiai,然后同时用 c r t crt crt合并。
先放 c r t crt crt代码:
ll crt(ll n, ll mod){
return n*(p/mod)%p*inv(p/mod, mod)%p;
}
p p p是给定的模数,就相当于 c r t crt crt中所有模数的乘积, m o d mod mod是当前的质因数分解出来的模数,就相当于单个方程的模数。
整段 e x L u c a s exLucas exLucas代码:
ll exlucas(){
ll t = p, ans = 0;
for(ll i = 2; i*i <= t; i++){ //质因数分解
if(t%i) continue;
ll tmp = 1;
while(t%i == 0){ //求出pi^ai
tmp *= i;
t /= i;
}
ans = (ans+crt(C(n, m, i, tmp), tmp))%p; //crt合并
}
if(t > 1) ans = (ans+crt(C(n, m, t, t), t))%p; //如果剩下的t是大质数,再进行一次计算
return ans%p;
}
洛谷P4720 【模板】扩展卢卡斯
#include
#define ll long long
using namespace std;
ll n, m, p;
ll exgcd(ll a, ll b, ll &x, ll &y){
if(!b){
x = 1, y = 0;
return a;
}
ll res = exgcd(b, a%b, x, y);
ll t = x;
x = y;
y = t-a/b*y;
return res;
}
ll qpow(ll a, ll n, ll mod){
ll res = 1;
while(n){
if(n&1) res = (res*a) % mod;
a = (a*a) % mod;
n >>= 1;
}
return res;
}
ll inv(ll a, ll p){
ll x, y;
exgcd(a, p, x, y);
if(x+p > p) return x;
return x+p;
}
ll crt(ll n, ll mod){
return n*(p/mod)%p*inv(p/mod, mod)%p;
}
ll fac(ll n, ll p, ll k){ //k = p^x
if(!n) return 1;
ll ans = 1;
for(int i = 2; i <= k; i++){
if(i%p) ans = ans*i % k;
}
ans = qpow(ans, n/k, k);
for(int i = 2; i <= n%k; i++){
if(i%p) ans = ans*i % k;
}
return ans*fac(n/p, p, k)%k;
}
ll C(ll n, ll m, ll p, ll k){ //k = p^x
if(n < m) return 0;
ll a = fac(n,p,k), b = fac(m,p,k), c = fac(n-m,p,k);
ll cnt = 0;
for(ll i = p; i <= n; i *= p) cnt += n/i;
for(ll i = p; i <= m; i *= p) cnt -= m/i;
for(ll i = p; i <= n-m; i *= p) cnt -= (n-m)/i;
return a*inv(b, k)%k * inv(c, k)%k * qpow(p, cnt, k)%k;
}
ll exlucas(){
ll t = p, ans = 0;
for(ll i = 2; i*i <= t; i++){
if(t%i) continue;
ll tmp = 1;
while(t%i == 0){
tmp *= i;
t /= i;
}
ans = (ans+crt(C(n, m, i, tmp), tmp))%p;
}
if(t > 1) ans = (ans+crt(C(n, m, t, t), t))%p;
return ans%p;
}
int main()
{
cin >> n >> m >> p;
cout << exlucas() << endl;
return 0;
}