Lucas 定理及其实现

本文不涉及定理的证明(作者太菜了不会)。

公式

Lucas 定理可以在模数是质数且不大的情况下求解大组合数取模问题:

对于素数 \(p\),有

\[(^{n}_{k}) \equiv (^{\lfloor {n \over p} \rfloor}_{\lfloor {k \over p} \rfloor})(^{n \mod p}_{k \mod p}) (mod p) \]

\(n < k\) 时,\((^{n}_{k})\) 规定为 0.

实现

由于模数不大,所以对于公式的后一部分 \((^{n \mod p}_{k \mod p})\) 我们可以通过预处理阶乘与阶乘逆元根据定义快速计算,前一部分 \((^{\lfloor {n \over p} \rfloor}_{\lfloor {k \over p} \rfloor})\) 就递归着去算:

int C(int n, int m) {
    i64 res = 1;
    // 由于 m <= n
    // 所以 m 变成 0 后组合数的结果就都是 1 了
    while (m) {
        // cnt(n, m) 是根据组合数的定义来计算 (n, m)
        (res *= cnt(n % mod, m % mod)) %= mod;
        n /= mod;
        m /= mod;
    }
    return res;
}

这里附上完整的实现:

class Lucas {
private:
    int mod;
    std::vector<i64> fa, ifa;
    
    int cnt(int n, int m) {
        if (n < m) {
            return 0;
        }
        return fa[n] * ifa[m] * ifa[n - m] % mod;
    }
public:
    // 根据模数预处理阶乘和阶乘逆元
    Lucas(int m): mod(m) {
        fa.assign(mod, 1ll);
        for (int i = 1; i < mod; i++) {
            fa[i] = fa[i - 1] * i % mod;
        }
        ifa.assign(mod, mod - 1); // 威尔逊定理
        for (int i = mod - 1; i > 0; i--) {
            ifa[i - 1] = ifa[i] * i % mod;
        }
    }

    int C(int n, int m) {
        i64 res = 1;
        while (m) {
            (res *= cnt(n % mod, m % mod)) %= mod;
            n /= mod;
            m /= mod;
        }
        return res;
    }
};

虽然本文不是专门讲威尔逊定理的,但是还是在这里解释以下为什么可以根据这个定理将 ifa 赋初始值 mod - 1(实际上只要 ifa[mod - 1] = mod - 1 就够了,其他的值无所谓)。

首先威尔逊定理是:

对于任何素数 \(p\) 有:

\[(p - 1)! + 1 \equiv 0(mod p) \]

即对于素数 \(p\),有: \((p - 1)! \% p = p - 1\)

那么这和阶乘的逆元有什么关系呢?

\((p - 1)!\) 的逆元为 \(I\),则根据逆元的定义,我们应该有:

\[I(p - 1)! \equiv 1(modp) \]

即(此步用了威尔逊定理):

\[I(p - 1) \% p = 1; \]

我们发现,\(I = p - 1\) 正好可以满足这个式子——代入这个值得到:

\[(p^2 - 2p + 1) \% p = 1; \]

所以 \((p - 1)!\) 的在模 \(p\) 时的逆元就是 \((p - 1)\)

posted @ 2025-04-03 13:29  Young_Cloud  阅读(64)  评论(0)    收藏  举报