卢卡斯定理及拓展

卢卡斯定理

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(logpnlog2p)

取模数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[n1]a[0] B = b [ n ] b [ n − 1 ] … b [ 0 ] B=b[n]b[n-1]…b[0] B=b[n]b[n1]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[n1]b[n1]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=p1a1p2a2...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. xCnm(mod  p1a1)xCnm(mod  p2a2)......xCnm(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!(nm)!n! mod  pk

因为求逆元的充要条件为 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1,因此不能对 m ! , ( n − m ) ! m!,(n-m)! m!,(nm)!求逆元,考虑不能求逆元时就是 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(nm)!pxn!pxyz 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(nm)f(n)pxyz 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!=(p2p3p...tp)(1...)

又因为 1 − n 1-n 1n中有 ⌊ 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*...) ppn!(12...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)~~① ppn!(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 (123456)(8910111213)15=(123456)(8%79%710%710%711%712%713%7)15=(123456)215

因此 ① ① 式又可以化为 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) ppn!(pn!)!(i=1,i%p!=0pki)pkn(i=pknpk,i%p!=0ni)

因为 p ⌊ n ! p ⌋ p^{\lfloor\frac{n!}{p}\rfloor} ppn!是要提取出来化为 p x − y − z p^{x-y-z} pxyz,但是 ( ⌊ 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=pknpk,i%p!=0ni),递归边界为 f ( 0 ) = 1 f(0)=1 f(0)=1

最后还需要求 p x − y − z p^{x-y-z} pxyz,设 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(nm),则 p x − y − z = p g ( n ) − g ( m ) − g ( n − m ) p^{x-y-z}=p^{g(n)-g(m)-g(n-m)} pxyz=pg(n)g(m)g(nm)

综上,总的公式为: 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(nm)f(n)pg(n)g(m)g(nm) 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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值