扩展卢卡斯定理

10 / 07 / 2020 | 最后修改于 05 / 24 / 2023

求:

(nm)modp\binom n m \bmod p

其中 pp 不保证为质数。


前置知识

中国剩余定理(CRT)

ExGCD求逆元


考虑令 p=p1k1p2k2p3k3p=p_1^{k_1}p_2^{k_2}p_3^{k_3} \dots

原问题转化为求:

{Cnmmodp1k1Cnmmodp2k2Cnmmodp3k3\begin{cases} C_n^m \bmod p_1^{k_1} \\ C_n^m \bmod p_2^{k_2} \\ C_n^m \bmod p_3^{k_3} \\ \dotsm \end{cases}

的一个解,使用中国剩余定理合并即可。

针对其中一个式子 CnmmodpkC_n^m \bmod p^{k}n!m!(nm)!modpk\frac {n!} {m!(n-m)!} \bmod p^k ,使用求逆元的方法是不行的,因为 pp 不保证为质数, m!(nm)!m!(n-m)! 可能不存在逆元。

f(n)=n!modpkf(n)=n! \bmod p^k ,进一步考虑,既然不互质,那就把他们的公因子都约掉,我们把阶乘展开看一看:

n!=1×2×3n=(p2p3p)[1×2×3(p1)(p+1)(p+2)]=pnp(1×2×3)[1×2×3(p1)][(p+1)(p+2))]=pnp(np)!pini=pnp(np)!(pipki)npkrest\begin{align} n! &= 1 \times 2 \times 3 \cdots n \\ &= (p \cdot 2p \cdot 3p \cdots) \cdot [1 \times 2 \times 3 \cdots (p-1) \cdot (p + 1) \cdot (p + 2) \cdots] \\ &= p^{\lfloor \frac{n}{p} \rfloor} (1 \times 2 \times 3 \cdots) \cdot [1 \times 2 \times 3 \cdots (p-1)] \cdot [(p + 1) \cdot (p + 2) \cdots)] \\ &= \large p^{\lfloor \frac{n}{p} \rfloor} (\frac{n}{p})! \prod_{p \nmid i}^n i \\ &= \large p^{\lfloor \frac{n}{p} \rfloor} (\frac{n}{p})! (\prod_{p \nmid i}^{p^k} i)^{\frac {n}{p^k}} \text{rest} \\ \end{align}

其中 rest\text{rest} 是指最后不够一整段 pkp^k 的剩下的项, (pipki)\displaystyle \large (\prod_{p \nmid i}^{p^k} i) 是在 modpk\bmod p^k 意义下的一个循环节,这样的循环节有 npk\frac n {p^k} 个。

每这样一次,我们都可以从阶乘提取出 pnpp^{\lfloor \frac n p \rfloor} 这个因子,并把原问题变为一个规模更小的问题 f(np)f(\frac n p) ,可以递归进行,并在过程中累加 np\lfloor \frac n p \rfloor ,记为 h(n)h(n) ,最后的 h(n)h(n) 就是 n!n!pp 这个因子的数量。

至此,我们成功把 n!n! 里的 pp 的因子分离出来。回归原来的问题 CnmmodpkC_n^m \bmod p^{k}

n!ph(n)m!ph(m)(nm)!ph(nm)ph(n)h(m)h(nm)modpk\large \frac {\frac {n!}{p^{h(n)}}} {\frac {m!}{p^{h(m)}}\frac {(n-m)!}{p^{h(n-m)}}} p^{h(n)-h(m)-h(n-m)} \bmod p^k

f(n)f(m)f(nm)ph(n)h(m)h(nm)modpk\frac{f(n)}{f(m)f(n-m)} p^{h(n)-h(m)-h(n-m)} \bmod p^k

由于 f(n)f(n) 已经是被约掉 pp 因子的,一定有逆元,于是CRT的一个式子就解决了,最后合并就好了。

Talk is cheap, show me your code

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <cstdio>

typedef long long ll;
const int N = 100000;
int p, cnt, pri[N], k[N], cpoy_p;
ll n, m;

inline ll qpow(ll base, ll exp, const int &mod) {
ll res = 1;
while (exp) {
if (exp & 1) res = res * base % mod;
base = base * base % mod;
exp >>= 1;
}
return res;
}

void exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) return x = 1, y = 0, void();
exgcd(b, a % b, y, x);
y -= a / b * x;
}

namespace CRT {

int a[N], ans = 0, pk;
ll inv, y;

inline int solve() {
for (int i = 1; i <= cnt; ++i) {
pk = qpow(pri[i], k[i], p + 1);
exgcd(p / pk, pk, inv, y);
ans += a[i] * inv % p * (p / pk) % p;
if (ans >= p) ans -= p;
if (ans < 0) ans += p;
}
return ans;
}

} // namespace CRT

namespace Exlucas {

int prime, mod; // mod = p^k
ll h; //指数也要开long long

inline void MOD(ll &x, const int &mod) {
if (x >= mod) x %= mod;
}

ll f(ll n) {
if (n == 0) return 1;
h += n / prime;
ll res = 1, tmp = 1;
res *= f(n / prime);
MOD(res, mod);
for (int i = 1; i <= mod; ++i) {
if (i % prime) (tmp *= i) %= mod;
}
res *= qpow(tmp, n / mod, mod);
MOD(res, mod);
for (ll i = n / mod * mod; i <= n; ++i) {
if (i % prime) {
res *= i % mod;
MOD(res, mod);
}
}
return res;
}

inline ll solve(int i) {
prime = pri[i], mod = qpow(prime, k[i], p + 1);
int exp = 0;
ll res = 1, inv, y;
h = 0, res *= f(n), exp += h;

h = 0, exgcd(f(m), mod, inv, y);
inv = (inv % mod + mod) % mod;
res *= inv;
MOD(res, mod);
exp -= h;

h = 0, exgcd(f(n - m), mod, inv, y);
inv = (inv % mod + mod) % mod;
res *= inv;
MOD(res, mod);
exp -= h;

return res * qpow(prime, exp, mod) % mod;
}

} // namespace Exlucas

signed main() {
#ifndef ONLINE_JUDGE
#ifdef LOCAL
freopen("testdata.in", "r", stdin);
freopen("testdata.out", "w", stdout);
#else
freopen("ExLucas.in", "r", stdin);
freopen("ExLucas.out", "w", stdout);
#endif
#endif

scanf("%lld%lld%d", &n, &m, &p);
cpoy_p = p;
for (int i = 2; i * i <= p; ++i) {
if (cpoy_p % i == 0) {
pri[++cnt] = i;
while (cpoy_p % i == 0) { cpoy_p /= i, k[cnt]++; }
}
}
if (cpoy_p != 1) pri[++cnt] = cpoy_p, k[cnt] = 1;
for (int i = 1; i <= cnt; ++i) { CRT::a[i] = Exlucas::solve(i); }
printf("%d", CRT::solve());
return 0;
}