# 组合数求法
# 方法一 :动态规划(取模值)
利用组合数公式:Cab=Ca−1b+Ca−1b−1 进行状态转移.
对于Cab=Ca−1b+Ca−1b−1 的理解:
Cab 的数学含义:从有a 个小球的箱子中随机抽b 个小球的方案数.
⇔ 1. 先从箱子中选一个小球,那么还需要从a−1 个小球中选b−1 个小球,方案数为Ca−1b−1. 2. 先从箱子中选一个小球扔掉,那么还需要从a−1 个小球中选b 个小球,方案数为C^{b}_
求解Cab 的值,结果对109−7 取模.
| int f[N][N], MOD = 1E9 + 7; |
| void getCombination(int n){ |
| for(int i = 0; i <= n; i ++ ) f[i][0] = 1; |
| for(int i = 1; i <= n; i ++ ) |
| for(int j = 1; j <= i; j ++ ) |
| f[i][j] = (f[i - 1][j] + f[i - 1][j - 1]) % MOD; |
| } |
- 时间复杂度:O(n2)
- 空间复杂度:O(n2)
运用场景:可以通过预处理求出Cab 范围内的所有组合数,然后通过O(1) 时间复杂度求出该组合数.
局限性:当a 很大时,预处理的时间和空间的复杂度都无法接受
相关题目:
# 方法二:费马小定理 + 快速幂(取模值)
利用组合数公式:Cab=b!a(a−1)⋯(a−b+1)=b!(a−b)!a(a−1)⋯(a−b+1)×(a−b)!=b!(a−b)!a!.
- 预处理出所以n!
- 利用费马小定理:Cab(modp)=b!(a−b)!a!(modp)=a!×inv[b!(a−b)!],其中inv(b!(a−b)!) 指代b!(a−b)! 关于p 的逆元
- 通过快速幂求解逆元
| constexpr int N = 1e5 + 10, MOD = 1E9 + 7; |
| ll f[N]; |
| ll qmi(ll a, ll b, ll q){ |
| ll res = 1; |
| while(b){ |
| if(b & 1) res = res * a % q; |
| b >>= 1; |
| a = a * a % q; |
| } |
| return res; |
| } |
| int C(int a, int b){ |
| return f[a] * qmi(f[b] * f[a - b] % MOD, MOD - 2, MOD) % MOD; |
| } |
| |
| |
| int main(){ |
| f[0] = 1; |
| for(int i = 1; i <= N - 1; i ++ ) f[i] = f[i - 1] * i % MOD; |
| int a, b; cin >> a >> b; |
| cout << C(a, b) << endl; |
| return 0; |
| } |
- 时间复杂度:预处理阶乘O(n), 求解组合数O(logn)
- 空间复杂度:存储阶乘数组O(n)
局限性:当a 超大时,预处理的时间和空间的复杂度都无法接受,例如a>109.
相关题目:
# 方法三:Lucas(卢卡斯) 定理(取模值)
# 定义
Lucas 定理内容如下:对于质数p ,有:
Cab(modp)=C⌊a/p⌋⌊b/p⌋⋅Ca%pb%p(modp)
# 证明
证明一:Cpm(modp)≡0(p为质素),其中m<p且m>0
Cpm(modp)=m!(p−m)!p!(modp)=mp⋅(m−1)!(p−m)!(p−1)!(modp)=mpCp−1m−1(modp).
由于m<p 且p 为质数,固m 关于p 的逆元inv(m) 一定存在.
∴Cpm(modp)=p⋅inv(m)⋅Cp−1m−1(modp)=0 得证.
证明二:结论证明
求解Cab⇔ 求解(1+x)a 的xb 的系数.
不妨设qa=⌊a/p⌋,ra=a%p,易知ra<p.
由于(1+x)a=(1+x)qa⋅p+ra=[(1+x)p]qa⋅(1+x)ra
又由证明一\Rightarrow (1+x)^{p}\,(mod\;p)=\sum_{i=0}^{p}C_{p}^{i}x^{i}\,(mod\;p)=1+x^
∴(1+x)a=(1+xp)qa⋅(1+x)ra
不妨设qb=⌊b/p⌋,rb=b%p,易知rb<p.
\Rightarrow C_{a}^
# 方法四:高精度(准确值)