Lucas 定理是怎么来的?
Lucas 定理的最直观证明是通过生成函数(母函数)和二项式定理 来推导的。
首先,根据二项式定理,我们知道组合数 C n m C_n^m C n m 其实就是多项式 ( 1 + x ) n (1+x)^n ( 1 + x ) n 展开后 x m x^m x m 这一项的系数。
对于任意质数 p p p ,考虑 ( 1 + x ) p ( m o d p ) (1+x)^p \pmod p ( 1 + x ) p ( m o d p ) 的展开式:
( 1 + x ) p = C p 0 x 0 + C p 1 x 1 + C p 2 x 2 + ⋯ + C p p x p (1+x)^p = C_p^0 x^0 + C_p^1 x^1 + C_p^2 x^2 + \dots + C_p^p x^p
( 1 + x ) p = C p 0 x 0 + C p 1 x 1 + C p 2 x 2 + ⋯ + C p p x p
因为 p p p 是质数,当 1 ≤ i ≤ p − 1 1 \le i \le p-1 1 ≤ i ≤ p − 1 时,C p i = p ! i ! ( p − i ) ! C_p^i = \frac{p!}{i!(p-i)!} C p i = i ! ( p − i ) ! p ! 的分子里有一个 p p p ,而分母里没有 p p p 可以把它约掉。所以中间所有的项都是 p p p 的倍数,在模 p p p 意义下全部等于 0 0 0 。
因此我们得到了一个非常漂亮的核心结论:
( 1 + x ) p ≡ 1 + x p ( m o d p ) (1+x)^p \equiv 1 + x^p \pmod p
( 1 + x ) p ≡ 1 + x p ( m o d p )
现在我们来求 ( 1 + x ) n ( m o d p ) (1+x)^n \pmod p ( 1 + x ) n ( m o d p ) 中 x m x^m x m 的系数。
我们将 n n n 和 m m m 拆分成除以 p p p 的商和余数(类似于把它们转成 p p p 进制):
令 n = ⌊ n / p ⌋ × p + ( n m o d p ) n = \lfloor n/p \rfloor \times p + (n \bmod p) n = ⌊ n / p ⌋ × p + ( n m o d p )
令 m = ⌊ m / p ⌋ × p + ( m m o d p ) m = \lfloor m/p \rfloor \times p + (m \bmod p) m = ⌊ m / p ⌋ × p + ( m m o d p )
代入多项式中:
由于前面证明的核心结论,把内部的 ( 1 + x ) p (1+x)^p ( 1 + x ) p 替换掉:
( 1 + x ) n = ( 1 + x ) ⌊ n / p ⌋ × p + ( n m o d p ) = ( ( 1 + x ) p ) ⌊ n / p ⌋ × ( 1 + x ) n m o d p
\begin{aligned}(1+x)^n &= (1+x)^{\lfloor n/p \rfloor \times p + (n \bmod p)} \\&= \left( (1+x)^p \right)^{\lfloor n/p \rfloor} \times (1+x)^{n \bmod p} \\\end{aligned}
( 1 + x ) n = ( 1 + x ) ⌊ n / p ⌋ × p + ( n m o d p ) = ( ( 1 + x ) p ) ⌊ n / p ⌋ × ( 1 + x ) n m o d p
( 1 + x ) n ≡ ( 1 + x p ) ⌊ n / p ⌋ × ( 1 + x ) n m o d p ( m o d p )
\begin{aligned}(1+x)^n &\equiv (1+x^p)^{\lfloor n/p \rfloor} \times (1+x)^{n \bmod p} \pmod p\end{aligned}
( 1 + x ) n ≡ ( 1 + x p ) ⌊ n / p ⌋ × ( 1 + x ) n m o d p ( m o d p )
我们要求的是 x m x^m x m 的系数,而 x m = ( x p ) ⌊ m / p ⌋ × x m m o d p x^m = (x^p)^{\lfloor m/p \rfloor} \times x^{m \bmod p} x m = ( x p ) ⌊ m / p ⌋ × x m m o d p 。
所以在右边这两个括号相乘时,我们必须:
从前一个括号 ( 1 + x p ) ⌊ n / p ⌋ (1+x^p)^{\lfloor n/p \rfloor} ( 1 + x p ) ⌊ n / p ⌋ 中选出 ( x p ) ⌊ m / p ⌋ (x^p)^{\lfloor m/p \rfloor} ( x p ) ⌊ m / p ⌋ ,这部分的系数是 C ⌊ n / p ⌋ ⌊ m / p ⌋ C_{\lfloor n/p \rfloor}^{\lfloor m/p \rfloor} C ⌊ n / p ⌋ ⌊ m / p ⌋ 。
从后一个括号 ( 1 + x ) n m o d p (1+x)^{n \bmod p} ( 1 + x ) n m o d p 中选出 x m m o d p x^{m \bmod p} x m m o d p ,这部分的系数是 C n m o d p m m o d p C_{n \bmod p}^{m \bmod p} C n m o d p m m o d p 。
两者相乘,就得到了 x m x^m x m 的总系数。这就是 Lucas 定理的来源:
C n m ≡ C ⌊ n / p ⌋ ⌊ m / p ⌋ × C n m o d p m m o d p ( m o d p ) C_{n}^{m} \equiv C_{\lfloor n / p \rfloor}^{\lfloor m / p \rfloor} \times C_{n \bmod p}^{m \bmod p} \pmod p
C n m ≡ C ⌊ n / p ⌋ ⌊ m / p ⌋ × C n m o d p m m o d p ( m o d p )
关于这道 AcWing 题目与你的模板
注意观察这道题目的输入格式:每一组询问都会给出一个不同的质数 p p p !
我们刚才在你的 main.tex 里,把 Lucas 定理改成了复用你已有的 Comb 命名空间和全局的 mod。那个模板只适用于所有询问的模数 p p p 固定不变 的情况(因为我们需要针对固定的 mod 预处理阶乘)。
对于这道题,因为 p p p 会变(最高到 1 0 5 10^5 1 0 5 ),所以我们不能用全局预处理 ,反而应该用我们刚才撤掉的那份按定义暴力算 的代码!因为暴力算单次是 O ( p ) O(p) O ( p ) 的,加上 Lucas 递归,总时间大概是 O ( p log p n ) O(p \log_p n) O ( p log p n ) ,这道题 n ≤ 20 n \le 20 n ≤ 2 0 组询问,最大运算量在 2 × 1 0 6 2 \times 10^6 2 × 1 0 6 左右,完全可以光速通过。
以下是可以直接通过这道题的 AC 代码,风格和我们刚才讨论的完全一致:
AC
https://www.acwing.com/problem/content/submission/code_detail/44735227/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <iostream> #include <vector> using namespace std;using ll = long long ;ll mod; namespace Comb{ vector<ll> frac, infrac; int ln = 1 ; bool initialized = false ; inline ll binpow (ll a, ll k) { ll res = 1 ; a %= mod; while (k > 0 ) { if (k & 1 ) res = (res * a) % mod; a = (a * a) % mod; k >>= 1 ; } return res; } void Comb (int n) { ln = n; frac.assign (n + 5 , 1 ); infrac.assign (n + 5 , 1 ); for (int i = 1 ; i <= ln; ++i) { frac[i] = (frac[i - 1 ] * i) % mod; } infrac[ln] = binpow (frac[ln], mod - 2 ); for (int i = ln - 1 ; i >= 0 ; --i) { infrac[i] = (infrac[i + 1 ] * (i + 1 )) % mod; } } inline ll CC (ll n, ll k) { if (k < 0 || k > n) return 0 ; if (!initialized){ Comb (mod - 1 ); initialized = true ; } ll res = frac[n]; res = (res * infrac[n - k]) % mod; res = (res * infrac[k]) % mod; return res; } } using namespace Comb;inline ll lucas (ll a, ll b) { if (a < mod && b < mod) return CC (a, b); return CC (a % mod, b % mod) * lucas (a / mod, b / mod) % mod; } void solve () { ll a, b; cin >> a >> b >> mod; initialized = false ; cout << lucas (a, b) << '\n' ; } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); int n; if (cin >> n) { while (n--) { solve (); } } return 0 ; }