组合数计算方法

目录

公式计算
对数计算
递推计算
逆元计算
卢卡斯定理

前言
公式计算

直接用组合数的公式
C m n = m ! n ! ( m − n ) ! C_{m}^{n} = \frac {m!} {n!(m-n)!} Cmn=n!(mn)!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(mn)!=x=1mlnxx=1nlnxx=1mnlnx
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=Cm1n1+Cm1n
复杂度 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!(mn)!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} Cmnm!inv(n!)inv[(mn)!](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 mn的逆元存在了(它们可能是 p p p的倍数)
p s ps ps: 对于 A ∗ x = 1 ( m o d p ) A * x = 1\pmod p Ax=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 Ap11(modp),可得 x = A p − 2 ( m o d p ) x = A^{p-2}\pmod p x=Ap2(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} Cmni=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} CmnCmmodpnmodpCm/pn/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没问题。
证明过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值