Description
求
(
n
m
)
m
o
d
p
\dbinom{n}{m}\bmod p
(mn)modp
其中
p
p
p 较小且 不保证
p
p
p 是质数。
Method
前置芝士:
- 中国剩余定理
因为 p p p 不为质数,所以得使用扩展卢卡斯定理( E x t e n d e d L u c a s , e x L u c a s \rm Extended\ Lucas,exLucas Extended Lucas,exLucas)。
Part 1:中国剩余定理
首先按照 唯一分解定理 将
p
p
p 分解质因数:
p
=
∏
i
=
1
k
q
i
α
i
p=\prod\limits_{i=1}^{k}q_i^{\alpha_i}
p=i=1∏kqiαi
如果我们能够求出
{
(
n
m
)
m
o
d
q
1
α
1
(
n
m
)
m
o
d
q
2
α
2
⋯
(
n
m
)
m
o
d
q
k
α
k
\begin{cases} \dbinom{n}{m}\bmod q_1^{\alpha_1}\\ \dbinom{n}{m}\bmod q_2^{\alpha_2}\\ \cdots\\ \dbinom{n}{m}\bmod q_k^{\alpha_k} \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧(mn)modq1α1(mn)modq2α2⋯(mn)modqkαk
那么因为
∀
i
≠
j
,
gcd
(
q
i
α
i
,
q
j
α
j
)
=
1
\forall i\ne j,\gcd(q_i^{\alpha_i},q_j^{\alpha_j})=1
∀i=j,gcd(qiαi,qjαj)=1,所以可以用 CRT
合并。
那么问题就变成了求出 ( n m ) m o d q i α i \dbinom{n}{m}\bmod q_i^{\alpha_i} (mn)modqiαi。
Part 2:移除分子分母中的质因数
( n m ) ≡ n ! m ! ( n − m ) ! ≡ n ! ⋅ inv ( m ! ) ⋅ inv [ ( n − m ) ! ] ( m o d q α ) \begin{aligned} \dbinom{n}{m} &\equiv \dfrac{n!}{m!(n-m)!}\\ &\equiv n!\cdot \operatorname{inv}(m!)\cdot \operatorname{inv}[(n-m)!] \pmod {q^{\alpha}} \end{aligned} (mn)≡m!(n−m)!n!≡n!⋅inv(m!)⋅inv[(n−m)!](modqα)
但 m ! , ( n − m ) ! m!,(n-m)! m!,(n−m)! 与 q α q^{\alpha} qα 不一定互质,所以不一定存在逆元。
我们将分子和分母中的 q q q 的倍数全部提出来,设 n ! , m ! , ( n − m ) ! n!,m!,(n-m)! n!,m!,(n−m)! 分别含 x , y , z x,y,z x,y,z 个质因数 q q q。
n ! m ! ( n − m ) ! = n ! q x m ! q y ⋅ ( n − m ) ! q z ⋅ q x − y − z ≡ n ! q x ⋅ inv ( m ! q y ) ⋅ inv [ ( n − m ) ! q z ] ( m o d q α ) \begin{aligned} \dfrac{n!}{m!(n-m)!} &=\dfrac{\dfrac{n!}{q^x}}{\dfrac{m!}{q^y}\cdot \dfrac{(n-m)!}{q^z}}\cdot q^{x-y-z}\\ &\equiv \dfrac{n!}{q^x}\cdot \operatorname{inv}\left(\dfrac{m!}{q^y}\right)\cdot \operatorname{inv}\left[\dfrac{(n-m)!}{q^z}\right]\pmod {q^{\alpha}} \end{aligned} m!(n−m)!n!=qym!⋅qz(n−m)!qxn!⋅qx−y−z≡qxn!⋅inv(qym!)⋅inv[qz(n−m)!](modqα)
这时 m ! q y , ( n − m ) ! q z \dfrac{m!}{q^y},\dfrac{(n-m)!}{q^z} qym!,qz(n−m)! 都与 q α q^{\alpha} qα 互质。
Part 3:计算
以 n ! n! n! 为例:
n
!
=
(
q
×
2
q
×
⋯
×
⌊
n
q
⌋
q
)
×
∏
q
∤
i
n
i
=
q
⌊
n
q
⌋
×
⌊
n
q
⌋
!
×
∏
q
∤
i
n
i
\begin{aligned} n! &=(q\times 2q\times \cdots\times \left\lfloor\dfrac{n}{q}\right\rfloor q)\times \prod\limits_{q\nmid i}^n i\\ &=q^{\left\lfloor\frac{n}{q}\right\rfloor}\times \left\lfloor\dfrac{n}{q}\right\rfloor!\times \prod\limits_{q\nmid i}^n i \end{aligned}
n!=(q×2q×⋯×⌊qn⌋q)×q∤i∏ni=q⌊qn⌋×⌊qn⌋!×q∤i∏ni
其中
q
⌊
n
q
⌋
q^{\left\lfloor\frac{n}{q}\right\rfloor}
q⌊qn⌋ 可用快速幂直接算,
⌊
n
q
⌋
!
\left\lfloor\dfrac{n}{q}\right\rfloor!
⌊qn⌋! 可递归求解,后面
∏
q
∤
i
n
i
\prod\limits_{q\nmid i}^n i
q∤i∏ni 就不好算了。
我们发现因为模数是 q α q^{\alpha} qα,又有 x + q α ≡ x ( m o d q α ) x+q^{\alpha}\equiv x\pmod{q^{\alpha}} x+qα≡x(modqα),所以可以将 q α q^{\alpha} qα 作为一个循环周期,暴力算出 ∏ q ∤ i q α i \prod\limits_{q\nmid i}^{q^{\alpha}}i q∤i∏qαi,然后循环实际上有 ⌊ n q α ⌋ \left\lfloor\dfrac{n}{q^{\alpha}}\right\rfloor ⌊qαn⌋ 个,直接用快速幂。
对于多出来的部分,长度一定小于 q α q^{\alpha} qα,也可以暴力算。
以
n
=
19
,
p
=
3
,
α
=
2
n=19,p=3,\alpha=2
n=19,p=3,α=2 为例:
19
!
=
1
×
2
×
3
×
4
×
5
×
6
×
7
×
8
×
9
×
10
×
11
×
12
×
13
×
14
×
15
×
16
×
17
×
18
×
19
=
(
1
×
2
×
4
×
5
×
7
×
8
)
×
(
10
×
11
×
13
×
14
×
16
×
17
)
×
19
×
(
3
×
6
×
9
×
12
×
15
×
18
)
=
(
1
×
2
×
4
×
5
×
7
×
8
)
×
(
10
×
11
×
13
×
14
×
16
×
17
)
×
19
×
3
6
×
(
1
×
2
×
3
×
4
×
5
×
6
)
≡
(
1
×
2
×
4
×
5
×
7
×
8
)
2
×
19
×
3
6
×
6
!
(
m
o
d
3
2
)
=
∏
3
∤
i
19
i
×
3
⌊
19
3
⌋
×
⌊
19
3
⌋
!
\begin{aligned} 19! &=1\times2\times3\times4\times5\times6\times7\times8\times9\times10\times11\times12\times13\times14\times15\times16\times17\times18\times19\\ &=(1\times2\times4\times5\times7\times8)\times(10\times11\times13\times14\times16\times17)\times19\times(3\times6\times9\times12\times15\times18)\\ &=(1\times2\times4\times5\times7\times8)\times(10\times11\times13\times14\times16\times17)\times19\times3^6\times(1\times2\times3\times4\times5\times6)\\ &\equiv (1\times2\times4\times5\times7\times8)^2\times19\times3^6\times6!\pmod {3^2}\\ &=\prod\limits_{3\nmid i}^{19}i\times 3^{\left\lfloor\frac{19}{3}\right\rfloor}\times \left\lfloor\dfrac{19}{3}\right\rfloor!~ \end{aligned}
19!=1×2×3×4×5×6×7×8×9×10×11×12×13×14×15×16×17×18×19=(1×2×4×5×7×8)×(10×11×13×14×16×17)×19×(3×6×9×12×15×18)=(1×2×4×5×7×8)×(10×11×13×14×16×17)×19×36×(1×2×3×4×5×6)≡(1×2×4×5×7×8)2×19×36×6!(mod32)=3∤i∏19i×3⌊319⌋×⌊319⌋!
再递归计算
6
!
m
o
d
3
2
6!\bmod 3^2
6!mod32 即可。
递归部分的代码如下:
ll cal(ll n, ll p, ll pa)
{
if (!n)
{
return 1;
}
ll ans = 1;
for (int i = 1; i <= pa; i++) //循环部分
{
if (i % p)
{
ans = ans * i % pa;
}
}
ans = qpow(ans, n / pa, pa);
for (int i = 1; i <= n % pa; i++) //多出部分
{
if (i % p)
{
ans = ans * i % pa;
}
}
return ans * cal(n / p, p, pa) % pa;
}
注意到这部分其实求的就是 n ! q x \dfrac{n!}{q^x} qxn!。
对于 m ! , ( n − m ) ! m!,(n-m)! m!,(n−m)! 的处理同理。
Part 4:合并
通过 Part 3 可以求出 n ! q x , m ! q y , ( n − m ) ! q z \dfrac{n!}{q^x},\dfrac{m!}{q^y},\dfrac{(n-m)!}{q^z} qxn!,qym!,qz(n−m)!,现在只需要求出 x , y , z x,y,z x,y,z 就可以得到 q x − y − z q^{x-y-z} qx−y−z 了。
怎么算想必小奥都讲过了吧,比如说
x
=
⌊
n
q
⌋
+
⌊
n
q
2
⌋
+
⌊
n
q
3
⌋
+
⋯
x=\left\lfloor\dfrac{n}{q}\right\rfloor+\left\lfloor\dfrac{n}{q^2}\right\rfloor+\left\lfloor\dfrac{n}{q^3}\right\rfloor+\cdots
x=⌊qn⌋+⌊q2n⌋+⌊q3n⌋+⋯
至此可求出
(
n
m
)
m
o
d
q
i
α
i
\dbinom{n}{m}\bmod q_i^{\alpha_i}
(mn)modqiαi,再结合 Part 1 使用 CRT
合并就完成了求解
(
n
m
)
m
o
d
p
\dbinom{n}{m}\bmod p
(mn)modp 的过程。
Code
//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;
ll qpow(ll a, ll b, ll p)
{
ll base = a, ans = 1;
while (b)
{
if (b & 1)
{
ans = ans * base % p;
}
base = base * base % p;
b >>= 1;
}
return ans;
}
ll cal(ll n, ll p, ll pa)
{
if (!n)
{
return 1;
}
ll ans = 1;
for (int i = 1; i <= pa; i++)
{
if (i % p)
{
ans = ans * i % pa;
}
}
ans = qpow(ans, n / pa, pa);
for (int i = 1; i <= n % pa; i++)
{
if (i % p)
{
ans = ans * i % pa;
}
}
return ans * cal(n / p, p, pa) % pa;
}
ll cnt_p(ll n, ll m, ll p)
{
ll cnt = 0;
for (ll i = p; i <= n; i *= p)
{
cnt += n / i;
}
for (ll i = p; i <= m; i *= p)
{
cnt -= m / i;
}
for (ll i = p; i <= n - m; i *= p)
{
cnt -= (n - m) / i;
}
return cnt;
}
ll x, y;
void exgcd(ll a, ll b)
{
if (!b)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b);
ll tmp = x;
x = y;
y = tmp - a / b * y;
}
ll inv(ll a, ll p)
{
exgcd(a, p);
x = (x % p + p) % p;
return x;
}
ll C(ll n, ll m, ll p, ll pa)
{
ll a = cal(n, p, pa), b = cal(m, p, pa), c = cal(n - m, p, pa), cnt = cnt_p(n, m, p);
return a * inv(b, pa) % pa * inv(c, pa) % pa * qpow(p, cnt, pa) % pa;
}
ll a[10], b[10];
ll CRT(int n)
{
ll m = 1;
for (int i = 1; i <= n; i++)
{
m *= a[i];
}
ll ans = 0;
for (int i = 1; i <= n; i++)
{
ll mi = m / a[i];
ll Mi = inv(mi, a[i]);
ans = (ans + b[i] * mi % m * Mi % m) % m;
}
return ans;
}
ll exLucas(ll n, ll m, ll p)
{
int k = 0;
for (ll i = 2; i * i <= p; i++)
{
if (p % i == 0)
{
a[++k] = 1;
while (p % i == 0)
{
a[k] *= i;
p /= i;
}
b[k] = C(n, m, i, a[k]);
}
}
if (p > 1)
{
a[++k] = p;
b[k] = C(n, m, p, p);
}
return CRT(k);
}
int main()
{
ll n, m, p;
scanf("%lld%lld%lld", &n, &m, &p);
printf("%lld\n", exLucas(n, m, p));
return 0;
}
Complexity
计算 n ! q x m o d q α \dfrac{n!}{q^x}\bmod q^{\alpha} qxn!modqα 的时间为 O ( q α log n ) O(q^{\alpha}\log n) O(qαlogn)。
计算 x x x 的时间为 O ( log n ) O(\log n) O(logn)。
所以计算 ( n m ) m o d q α \dbinom{n}{m}\bmod q^{\alpha} (mn)modqα 的时间为 O ( q α log n ) O(q^{\alpha}\log n) O(qαlogn)。
整个预处理部分就是 O ( p + ∑ i = 1 k q i α i log n ) O(\sqrt{p}+\sum\limits_{i=1}^k q_i^{\alpha_i}\log n) O(p+i=1∑kqiαilogn)。
CRT
部分是
O
(
k
log
∏
i
=
1
k
q
i
α
i
)
=
O
(
k
log
p
)
O(k\log \prod\limits_{i=1}^k q_i^{\alpha_i})=O(k\log p)
O(klogi=1∏kqiαi)=O(klogp) 的。
综上,exLucas
的时间复杂度为
O
(
p
+
∑
i
=
1
k
q
i
α
i
log
n
+
k
log
p
)
∼
O
(
p
log
n
)
O(\sqrt{p}+\sum\limits_{i=1}^k q_i^{\alpha_i} \log n+k\log p)\sim O(p\log n)
O(p+i=1∑kqiαilogn+klogp)∼O(plogn)。
Problems
模板
模板 + 乘法原理
答案为 ( n w 1 ) × ( n − w 1 w 2 ) × ( n − w 1 − w 2 w 3 ) × ⋯ m o d P \dbinom{n}{w_1}\times \dbinom{n-w_1}{w_2}\times \dbinom{n-w_1-w_2}{w_3}\times \cdots\bmod P (w1n)×(w2n−w1)×(w3n−w1−w2)×⋯modP。
References
- [1] JokerJim:【算法】扩展卢卡斯详解
- [2] YangTY:Lucas 定理
- [3] OI Wiki:卢卡斯定理