快速求组合数
同余定理
给定一个正整数 m,如果两个整数 a 和 b 满足 a - b 能够被 m 整除,即 a − b m \frac{a-b}{m} ma−b 得到一个整数,那么就称整数a 与 b 对模 m 同余,记作 a ≡ b (mod m) 。对模m同余是整数的一个等价关系。
模的运算
取模运算的等价变形适合加法、减法、乘法
(a + b) % p = (a % p + b % p) % p
(a - b) % p = (a % p - b % p) % p
(a * b) % p = (a % p * b % p) % p
取模操作,模一次和模多次结果不变
逆元
我认为原本你在某个世界的原点,由于某些操作被偏移了一定的位置 X ,后来你又通过施加了某些操作 Y 又回到了原点。
在加法世界中,0 就是原点,你偏移了 + 3 的位置,可以通过施加一个 - 3 回到原点。那么 - 3 就是 3 的逆元
乘法逆元:
- 模p意义下,一个数a如果有逆元x,那么除以a相当于乘以x。
- 在模 n 的意义下,a 存在逆元的充要条件是:n 不等于1,且 gcd(a,n) = 1 (a,n)互质
满足 b · k ≡ 1 (mod p)
的 k 的值就是 b 关于 p 的乘法逆元
根据同余的性质
b
×
k
−
1
p
=
z
(
z
是
一
个
整
数
)
b
×
k
=
z
p
+
1
(
b
×
k
)
%
p
=
1
\frac{b \times k - 1}{p} = z\\(z是一个整数)\\ b \times k = zp + 1\\ (b \times k) \% p = 1
pb×k−1=z(z是一个整数)b×k=zp+1(b×k)%p=1
即 $ (b \times k) % p = 1$ 和
b
×
k
≡
1
(
m
o
d
p
)
b\times k\equiv 1 \ (mod\ p)
b×k≡1 (mod p) 等价
根据存在逆元的充分条件,则b、k 与模 p 互质
费马小定理
如果 p 是一个质数,而整数 a 不是 p 的倍数,则有 ap-1 ≡ 1(mod p)
这也暗示了 p 和 a 是互质的。p 只有 p 和 1 两个因数,且 a 不是 p 的倍数,这就代表 p 和 a 互质
ap-1 ≡ 1(mod p) 可以转化为 a p − 1 % p = 1 a ^{p-1} \ \% \ p = 1 ap−1 % p=1
推算
一、
当我们要求(a / b) mod p的值,且 a 很大,无法直接求得a / b的值时,我们就要用到乘法逆元
同时由于除法没有mod的运算规则,无法进行化简运算
我们可以计算 b 关于 模 p 的逆元 c 使得 (a / b) mod p
与 (a × c) mod p
等价
假设已知 b 关于模 p 的逆元是 c,即 $ (b \times c) % p = 1$
a
b
%
p
=
a
b
×
1
%
p
=
a
b
×
(
b
×
c
)
%
p
=
(
a
×
c
)
%
p
=
(
a
%
p
×
c
%
p
)
%
p
\frac{a}{b} \ \% \ p \\ = \frac{a}{b} \times 1 \ \% \ p \\ =\frac{a}{b} \times (b \times c)\ \% \ p \\ = (a \times c) \ \% \ p\\ = (a \ \% \ p \ \times c \ \% \ p) \ \% \ p
ba % p=ba×1 % p=ba×(b×c) % p=(a×c) % p=(a % p ×c % p) % p
进而通过 a % p 和 c % p 会使得模后的结果落入 p 的大小区间,便于计算
二、
在上面的费马小定理中,我们得到了 a p − 1 % p = 1 a ^{p-1} \ \% \ p = 1 ap−1 % p=1
即 a p − 2 × a % p = 1 a^{p-2} \times a \ \% \ p = 1 ap−2×a % p=1,显然 ap-2 就是 a 的逆元,可以通过快速幂求法得到算的 a
三、
现在目标是求
C
n
m
%
p
C_n^m \ \% \ p
Cnm % p,p为素数(经典p=19+7)
C
n
m
=
n
!
m
!
(
n
−
m
)
!
C_n^m \ = \frac{n!}{m!(n-m)!}
Cnm =m!(n−m)!n!
但由于取模的性质对于除法不适用,则有
C
n
m
%
p
≠
n
!
%
p
m
!
%
p
×
(
n
−
m
)
!
%
p
%
p
C_n^m \ \% \ p \ \neq \frac{n!\%p}{m!\%p\times(n-m)!\%p}\%p
Cnm % p =m!%p×(n−m)!%pn!%p%p
需要利用逆元把“除法”转换成“乘法”,才能借助取模的性质计算组合数
设 M 为 m! 关于模p的逆元,NM为 (n-m)! 关于模 p 的逆元,则
(
M
×
m
!
)
%
p
=
1
(
N
M
×
(
n
−
m
)
!
)
%
p
=
1
(M \times m!) \% p = 1\\ (NM \times (n-m)!) \% p = 1
(M×m!)%p=1(NM×(n−m)!)%p=1
C
n
m
%
p
=
n
!
m
!
(
n
−
m
)
!
%
p
=
n
!
m
!
(
n
−
m
)
!
×
(
M
×
m
!
)
%
p
×
(
N
M
×
(
n
−
m
)
!
)
%
p
%
p
=
n
!
×
M
%
p
×
N
M
%
p
C_n^m \ \% \ p \ = \frac{n!}{m!(n-m)!} \ \% \ p \\ = \frac{n!}{m!(n-m)!} \times (M \times m!) \% p \times (NM \times (n-m)!) \% p \ \% \ p \\ = n!\times M \ \% \ p \times NM \ \% \ p
Cnm % p =m!(n−m)!n! % p=m!(n−m)!n!×(M×m!)%p×(NM×(n−m)!)%p % p=n!×M % p×NM % p
在第二步到第三步省略了好几个模P,因为在上面计算阶乘过程中,我们不断的取模,阶乘结果一定是小于 p的,模一次和模多次结果没区别,但是
n
!
×
M
n! \times M
n!×M 有可能会越过 p ,因此再次取模
package b
import "fmt"
const MaxNumber = 100000
const ModNumber = 1000000009 // 模
var factorial [MaxNumber + 5]int64 // 存储组合数
func init() {
factorial[0] = 1
for i := 1; i < MaxNumber+5; i++ {
factorial[i] = factorial[i-1] * int64(i) % ModNumber
}
}
// 快速幂算法 base 底数 n 指数 mod 取模
func quickPow(base, n, mod int64) int64 {
var res int64 = 1
for n > 0 {
if (n & 1) == 1 {
res = res * base % mod
}
base = base * base % mod
n >>= 1
}
return res
}
func CCom(n, m int) {
fmt.Printf("C(n,m) = %d\n",
factorial[n]*quickPow(factorial[m], ModNumber-2, ModNumber)%ModNumber*quickPow(factorial[n-m], ModNumber-2, ModNumber)%ModNumber)
}