目录
前言
公式计算
直接用组合数的公式
C
m
n
=
m
!
n
!
(
m
−
n
)
!
C_{m}^{n} = \frac {m!} {n!(m-n)!}
Cmn=n!(m−n)!m!
但是很容易溢出,
21
!
=
51090942171709440000
21! = 51090942171709440000
21!=51090942171709440000
对数计算
利用取对数,求前缀和
ln
C
m
n
=
ln
m
!
−
ln
n
!
−
ln
(
m
−
n
)
!
=
∑
x
=
1
m
ln
x
−
∑
x
=
1
n
ln
x
−
∑
x
=
1
m
−
n
ln
x
\ln C_{m}^{n} = \ln m! - \ln n! - \ln(m-n)! = \sum_{x=1}^{m}\ln x - \sum_{x=1}^{n}\ln x - \sum_{x=1}^{m-n}\ln x
lnCmn=lnm!−lnn!−ln(m−n)!=x=1∑mlnx−x=1∑nlnx−x=1∑m−nlnx
用
O
(
n
)
O(n)
O(n)的时间来求
ln
x
\ln x
lnx的前缀,但是可能有浮点误差。
递推计算
利用递推式
C
m
n
=
C
m
−
1
n
−
1
+
C
m
−
1
n
C_{m}^{n} = C_{m-1}^{n-1} + C_{m-1}^{n}
Cmn=Cm−1n−1+Cm−1n
复杂度
O
(
n
2
)
O(n^2)
O(n2),不算太好
逆元计算
通常题目会告诉一个模数
p
p
p,这个时候就可以用乘法逆元把除法转化成来计算,于是回到最开始的式子
C
m
n
=
m
!
n
!
(
m
−
n
)
!
C_{m}^{n} = \frac {m!} {n!(m-n)!}
Cmn=n!(m−n)!m!
把除法转化成乘法逆元
C
m
n
≡
m
!
⋅
i
n
v
(
n
!
)
⋅
i
n
v
[
(
m
−
n
)
!
]
(
m
o
d
p
)
C_m^n\equiv m!\cdot\mathrm{inv}(n!)\cdot\mathrm{inv}[(m-n)!]\pmod{p}
Cmn≡m!⋅inv(n!)⋅inv[(m−n)!](modp)
这样就可以
O
(
l
o
g
n
)
O(log_{n})
O(logn)求逆元和
O
(
n
)
O(n)
O(n)预处理,但是遇到
m
>
p
m > p
m>p的情况就不能保证
n
n
n和
m
−
n
m-n
m−n的逆元存在了(它们可能是
p
p
p的倍数)
p
s
ps
ps: 对于
A
∗
x
=
1
(
m
o
d
p
)
A * x = 1\pmod p
A∗x=1(modp),称
A
A
A关于
1
1
1模
p
p
p的乘法逆元为
x
x
x,又通过费马小定理
A
p
−
1
≡
1
(
m
o
d
p
)
A^{p-1} \equiv 1 \pmod p
Ap−1≡1(modp),可得
x
=
A
p
−
2
(
m
o
d
p
)
x = A^{p-2}\pmod p
x=Ap−2(modp)
卢卡斯定理
对于非负整数 m , n m,n m,n和质数 p p p, C m n ≡ ∏ i = 0 k C m i n i ( m o d p ) C_m^n\equiv\prod_{i=0}^{k}C_{m_i}^{n_i}\pmod{p} Cmn≡∏i=0kCmini(modp),其中
m = m k p k + ⋯ + m 1 p + m 0 m=m_kp^k+\cdots +m_1p+m_0 m=mkpk+⋯+m1p+m0
n = n k p k + ⋯ + n 1 p + n 0 n=n_kp^k+\cdots +n_1p+n_0 n=nkpk+⋯+n1p+n0
是 n , m n,m n,m的 p p p进制展开
通常使用如下公式计算
C
m
n
C_m^n
Cmn
C
m
n
≡
C
m
m
o
d
p
n
m
o
d
p
⋅
C
⌊
m
/
p
⌋
⌊
n
/
p
⌋
(
m
o
d
p
)
C_m^n\equiv C_{m\bmod p}^{n \bmod p}\cdot C_{\lfloor m/p\rfloor}^{\lfloor n/p\rfloor}\pmod{p}
Cmn≡Cmmodpnmodp⋅C⌊m/p⌋⌊n/p⌋(modp)
注意:当
m
<
n
m<n
m<n时,规定
C
m
n
=
0
C^n_m=0
Cmn=0
使用上面的公式就可以利用递归求解,当n=0时退出。
上板子
// 需要先预处理出阶乘fact[],利用快速幂求逆元inv
inline ll C(ll m, ll n, ll p)
{
return m < n ? 0 : fact[m] * inv(fact[n], p) % p * inv(fact[m - n], p) % p;
}
inline ll lucas(ll m, ll n, ll p)
{
return n == 0 ? 1 % p : lucas(m / p, n / p, p) * C(m % p, n % p, p) % p;
}
逆元+预处理,复杂度是
O
(
p
+
l
o
g
p
m
)
O(p+log_pm)
O(p+logpm),
1
0
6
10^6
106没问题。
证明过程