【ACWing】887. 求组合数 III

题目地址:

https://www.acwing.com/problem/content/889/

给定 n n n组询问,每次询问给定三个正整数 a a a b b b p p p,其中 p p p是素数。求 ( a b ) m o d    p {a\choose b}\mod p (ba)modp

数据范围:
1 ≤ n ≤ 20 1\le n\le 20 1n20
1 ≤ b ≤ a ≤ 1 0 18 1\le b\le a\le 10^{18} 1ba1018
1 ≤ p ≤ 1 0 5 1\le p\le 10^5 1p105

先介绍Lucas定理,对于非负整数 m m m n n n和素数 p p p,有: ( m n ) ≡ ( m m o d    p n m o d    p ) ( m / p n / p ) ( m o d    p ) {m \choose n}\equiv {m\mod p\choose n\mod p}{m/p\choose n/p}(\mod p) (nm)(nmodpmmodp)(n/pm/p)(modp)换言之,有: ( m n ) ≡ ∏ i = 0 k ( m i n i ) ( m o d    p ) {m \choose n}\equiv \prod_{i=0}^{k}{m_i \choose n_i}(\mod p) (nm)i=0k(nimi)(modp)其中 m = m k p k + m k − 1 p k − 1 + . . . + m 1 p + m 0 m=m_kp^k+m_{k-1}p^{k-1}+...+m_1p+m_0 m=mkpk+mk1pk1+...+m1p+m0 n = n k p k + n k − 1 p k − 1 + . . . + n 1 p + n 0 n=n_kp^k+n_{k-1}p^{k-1}+...+n_1p+n_0 n=nkpk+nk1pk1+...+n1p+n0即把 m m m n n n写成 p p p进制整数。证明如下,由于 p p p是素数,所以 ( 1 + x ) p k ≡ 1 + x p k ( m o d    p ) (1+x)^{p^k}\equiv 1+x^{p^k}(\mod p) (1+x)pk1+xpk(modp)。考虑 ( 1 + x ) m (1+x)^m (1+x)m,有: ( 1 + x ) m k p k + m k − 1 p k − 1 + . . . + m 1 p + m 0 = ( ( 1 + x ) p k ) m k ( ( 1 + x ) p k − 1 ) m k − 1 . . . ( ( 1 + x ) p ) m 1 ( 1 + x ) m 0 ≡ ( 1 + x p k ) m k ( 1 + x p k − 1 ) m k − 1 . . . ( 1 + x p ) m 1 ( 1 + x ) m 0 ( m o d    p ) (1+x)^{m_kp^k+m_{k-1}p^{k-1}+...+m_1p+m_0}\\=((1+x)^{p^k})^{m_k}((1+x)^{p^{k-1}})^{m_{k-1}}...((1+x)^p)^{m_1}(1+x)^{m_0}\\\equiv (1+x^{p^k})^{m_k}(1+x^{p^{k-1}})^{m_{k-1}}...(1+x^p)^{m_1}(1+x)^{m_0}(\mod p) (1+x)mkpk+mk1pk1+...+m1p+m0=((1+x)pk)mk((1+x)pk1)mk1...((1+x)p)m1(1+x)m0(1+xpk)mk(1+xpk1)mk1...(1+xp)m1(1+x)m0(modp)考虑 x n x^n xn的系数,有: ( m n ) ≡ ∏ i = 0 k ( m i n i ) ( m o d    p ) {m\choose n}\equiv \prod_{i=0}^{k}{m_i \choose n_i}(\mod p) (nm)i=0k(nimi)(modp) ≡ \equiv 左边是显然的,其右边,由于当 k ≥ p k\ge p kp的时候有 ( m k ) ≡ 0 ( m o d    p ) {m\choose k}\equiv 0(\mod p) (km)0(modp),所以可以只考虑 k < p k<p k<p的时候的系数。

接下来只需要套用上面的公式即可,要求 ( m n ) m\choose n (nm),对于 ( m m o d    p n m o d    p ) {m\mod p\choose n\mod p} (nmodpmmodp)可以直接求,而对于 ( m / p n / p ) {m/p\choose n/p} (n/pm/p)可以递归地去求。代码如下:

#include <iostream>
using namespace std;

int p;

// 快速幂
int fast_pow(int a, int k) {
    
    
    int res = 1;
    while (k) {
    
    
        if (k & 1) res = (long) res * a % p;
        a = (long) a * a % p;
        k >>= 1;
    }

    return res;
}

int C(int a, int b) {
    
    
    int res = 1;
    // 对于res,从a向下乘b步,然后除以b阶乘(乘以其逆元)
    for (int i = 1, j = a; i <= b; i++, j--) {
    
    
        res = (long) res * j % p;
        res = (long) res * fast_pow(i, p - 2) % p;
    }

    return res;
}

int lucas(long a, long b) {
    
    
    if (a < p && b < p) return C(a, b);
    return (long) C(a % p, b % p) * lucas(a / p, b / p) % p;
}

int main() {
    
    
    int n;
    cin >> n;

    while (n--) {
    
    
        long a, b;
        cin >> a >> b >> p;
        cout << lucas(a, b) << endl;
    }

    return 0;
}

时间复杂度 O ( log ⁡ p N N log ⁡ p ) = O ( N log ⁡ N ) O(\log_pN N\log p)=O(N\log N) O(logpNNlogp)=O(NlogN),空间 O ( log ⁡ p N ) O(\log_pN) O(logpN) N N N是输入范围。

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/113967261
今日推荐