要求:求
C
n
m
%
p
C_{n}^m \% p
Cnm%p ,p为素数(经典p=1e9+7)
【思路】
不取余又容易溢出,因此不能直接使用该公式。
方法一:DP
C[n][m]=
C
n
m
C_{n}^m
Cnm
公式
C
n
m
=
C
n
−
1
m
−
1
+
C
n
−
1
m
C_{n}^m=C_{n-1}^ {m-1}+C_{n-1}^{m}
Cnm=Cn−1m−1+Cn−1m
证明:班级中有n个人,选出m个人开除,有两种选法:
- 班长不开除,从剩下的人中开除m个人
- 开除班长,从剩下的人中开除m-1个人
const int MOD = 1e9 + 7;
int C[1005][1005]{};
C[0][0] = 1;
for (int i = 1; i <= 1000; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
时间复杂度:O(n²)
方法二:逆元+快速幂
如果能把“除法”转换成“乘法”,就可以借助取模的性质在不爆long long的情况下计算组合数。这时候就需要用到“逆元”!
逆元:对于a和p,若a*b%p≡1,则称b为a%p的逆元。
那这个逆元有什么用呢?
试想一下求
a
b
%
p
\cfrac{a}{b} \%p
ba%p,如果你知道b%p的逆元是c ,即(bc%p≡1),那么就可以转变成
a
b
%
p
=
a
b
∗
(
b
c
%
p
)
%
p
=
a
∗
c
%
p
=
(
a
%
p
)
(
c
%
p
)
%
p
\cfrac{a}{b} \%p = \cfrac{a}{b}*(bc\%p) \%p=a*c\%p = (a\%p)(c\%p)\%p
ba%p=ba∗(bc%p)%p=a∗c%p=(a%p)(c%p)%p
求逆元就需要引入费马小定理:
假如p是质数,且gcd(a,p)=1,那么 a^(p-1)≡1(mod p)。即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。
简要概述:
若c为b的逆元,即
c
∗
b
%
p
≡
1
c*b\%p≡1
c∗b%p≡1,则
a
b
%
p
=
a
∗
c
%
p
\cfrac{a}{b} \%p =a*c\%p
ba%p=a∗c%p
逆元求法:
c
=
b
p
−
2
c=b^{p-2}
c=bp−2
//逆元+快速幂
const int MOD = 1e9 + 7;
using ll = long long;
ll fac[1000 + 5];//阶乘,fac[i]表示 i!%MOD
ll rev[1000 + 5];//逆元
//快速幂求x^n%MOD
ll quickPow(ll x, ll n) {
ll res = 1;
while (n) {
if (n & 1)
res = res * x % MOD;
x = x * x % MOD;
n >>= 1;
}
return res;
}
int C(int n, int m) {
return fac[n] * rev[m] % MOD * rev[n-m] % MOD;
}
int main() {
//预处理求fac,fac[i] = i!%p
fac[0] = 1,rev[0]=1;
for (int i = 1; i <= 1000; i++){
fac[i] = fac[i - 1] * i % MOD;
rev[i] = quickPow(fac[i] ,MOD-2);
}
cout << C(1000, 100);
getchar();
exit(0);
}
l
o
g
2
(
1
e
9
+
7
)
≈
30
log_2(1e9+7)≈30
log2(1e9+7)≈30
时间复杂度:O(n)