Lucas定理及其扩展原理及应用

Lucas定理及其扩展原理及应用

Lucas定理(Lucas’s theorem)

内容

对于非负整数m和n和素数p,有
\mathrm{C}_n^m \equiv \prod_{i=0}^k \mathrm{C}_{n_i}^{m_i} \pmod{p}
其中,m = m_kp^k + m_{k-1}p^{k-1} + \cdots + m_1p + m_0n = n_kp^k + n_{k-1}p^{k-1} + \cdots + n_1p + n_0
也就是说,这个是m和n的p进制展开。
m < n时,\mathrm{C}_n^m = 0

求组合数的值

既然我们可以把m和n以p进制展开求值,那么我们可以设计递归来解决这个问题。
我们知道\mathrm{C}_n^0 = 1,那么以m=0为递归终点即可。代码如下。

inline LL lucas(LL n, LL m, LL p) {
    if(!m) return 1;
    return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}

其中C就是直接求组合数值的函数,这个可以预处理杨辉三角或者预处理阶乘及阶乘逆元。

例题:【P3807】【模板】卢卡斯定理 – 洛谷

直接应用上述方法即可。代码如下。

// Code by KSkun, 2018/3
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005;

LL T, n, m, p, inv[MAXN], mul[MAXN];

inline LL C(LL n, LL m, LL p) {
    if(m > n) return 0;
    return mul[n] * inv[n - m] % p * inv[m] % p;
}

inline LL lucas(LL n, LL m, LL p) {
    if(!m) return 1;
    return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}

inline void init() {
    mul[0] = inv[0] = mul[1] = inv[1] = 1;
    for(int i = 2; i <= std::min(n, p); i++) {
        inv[i] = -(p / i) * inv[p % i] % p;
    }
    for(int i = 2; i <= std::min(n, p); i++) {
        inv[i] = inv[i] * inv[i - 1] % p;
        mul[i] = mul[i - 1] * i % p;
    }
}

int main() {
    T = readint();
    while(T--) {
        n = readint(); m = readint(); p = readint(); n += m;
        init();
        printf("%lld\n", (lucas(n, m, p) + p) % p);
    }
    return 0;
}

扩展Lucas定理(exLucas)

内容

扩展Lucas定理与Lucas定理的区别是模数非质数,此时,我们考虑将模数质因数分解如下。
P = p_1^{k_1} \cdot p_2^{k_2} \cdots p_n^{k_n}
只要我们能求出组合数对于每个p_i^{k_i}的余数,我们就可以利用中国剩余定理(中国剩余定理及其扩展原理及应用 | KSkun’s Blog)解决这个问题。也就是说,我们将问题变为了这个:
\begin{cases} \mathrm{C}_n^m \equiv a_1 \pmod{p_1^{k_1}} \\ \mathrm{C}_n^m \equiv a_2 \pmod{p_2^{k_2}} \\ \vdots \\ \mathrm{C}_n^m \equiv a_n \pmod{p_n^{k_n}} \end{cases}
现在的问题是,上面每个式子的余数怎么求出。
如果我们使用组合数的定义,我们可以分别求出n! \bmod p_i^{k_i}, m! \bmod p_i^{k_i}, (n-m)! \bmod p_i^{k_i}
首先,我们知道阶乘中存在一些p_i因子,对于这些因子我们可以处理出来单独运算。对于提取一个因子后的阶乘,我们观察它们的规律。假如p=3,要求10!,提取后就变成了:
1 \times 2 \times 1 \times 4 \times 5 \times 2 \times 7 \times 8 \times 3 \times 10 \times 3^3
p_i的倍数组成了一个新的阶乘,这一部分我们递归下去处理。考虑剩下的部分,即1 \times 2 \times \cdots \times (p_i^{k_i}-1) \times (p_i^{k_i}+1) \times \cdots \times (2p_i^{k_i}-1) \times \cdots。我们发现\prod_{i=1, i \bmod p_i \neq 0}^{p_i^{k_i}-1} i \equiv \prod_{i=p_i^{k_i}+1, i \bmod p_i \neq 0}^{2p_i^{k_i}-1} i \pmod{p_i^{k_i}},也就是说,只需要求一个长为p_i^{k_i}的阶乘,并且计算有多少完整的一段,使用快速幂即可求出。至于多出去的那段,直接暴力乘进去即可。

例题:[国家集训队]礼物 题解 | KSkun’s Blog

代码参见该题题解文章。代码内有注释,可以对照上述内容理解。

参考资料



发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据