加法和乘法的取模
加法和乘法取模具有幂等性,直接计算即可。
需要注意的是,如果一行代码涉及三个(以上) 的乘法,需要注意是否会爆数据范围,保险的写法是 a * b % MOD * c % MOD。
同余
若 x 和 y 对于 m 同余,那么记作x ≡ y (mod m)
性质:
- 同余式中的加减法可以进行移项操作
减法的取模
根据同余的性质,可得一般地减法取模方法:
(x mod m + m) mod m
(a - b) mod m = ((a mod m - b mod m) + m) mod m。
这个公式不仅适用于 a - b <= 0也使用于大于零的情况。
除法的取模
- 结论:a 是 b 的倍数,并且 b 和 p 互质(b 不是 p 的倍数)
a / b ≡ (a * b ^ (p-2)) (mod p) - 引理 1:当 p 是质数且 1 <= i < p,有
C(p, i) ≡ 0 (mod p) - 引理 2:x、y 是任意整数,p 是任意质数,有
(x + y) ^ p ≡ x ^ p + y ^ p (mod p) - 费马小定理:
b ^ p ≡ b (mod p)
组合数的取模
计算组合数时,需要预先计算出阶乘和逆元,接着套公式即可。
C(n, m) = n! * 1 / (n - m)! * 1 / m!
const MOD = 1_000_000_007
const mx = 1_000_01 // 最大值
var fac[mx] int // 阶乘
var invF[mx] int // 阶乘的逆元 invF[i] = invF[i+1] * (i + 1)
func init() {
fac[0] = 1
for i := 1; i < mx; i++ {
fac[i] = i * fac[i-1] % MOD
}
invF[mx-1] = power(fac[mx-1], MOD-2)
for i := mx-1; i > 0; i-- {
invF[i-1] = invF[i] * i % MOD
}
}
func power(b, q int) int {
res := 1
for ;q > 0; q /= 2 {
if q & 1 == 1 {
res = res * b % MOD
}
b = b * b % MOD
}
return res
}
// 从 n 中选取 m 的方案数
func comb(n, m int) int {
if m < 0 || m > n {
return 0
}
return fac[n] * invF[m] % MOD * invF[n - m] % MOD
}
组合模 10
最近刷到一些题目考察组合数模 10,由于 10 并不是质数,因此不能使用费马小定理计算逆元。需要采用其他方式计算模 10 情况下的阶乘逆元。
核心思想:计算n的逆元,将n转换为n' * 2^q * 5^p,于是有n^-1 = n'^-1 * 2^-q * 5^-p。因为n'和 10 互质,即gcd(n', 10) = 1,所以可以通过欧拉定理计算n'^-1。
const MOD = 10
const mx = 1_000_01 // 最大值
var fac[mx] int // 阶乘
var invF[mx] int // 阶乘的逆元 invF[i] = invF[i+1] * (i + 1)
var p2[mx] int // 记录 2 的幂次
var p5[mx] int // 记录 5 的幂次
func init() {
fac[0] = 1
for i := 1; i < mx; i++ {
e2 := bits.TrailingZeros(uint(i))
x := i >> e2
e5 := 0
for x % 5 == 0 {
e5++
x /= 5
}
fac[i] = x * fac[i-1] % MOD
p2[i] = p2[i-1] + e2
p5[i] = p5[i-1] + e5
}
invF[mx-1] = power(fac[mx-1], 3) // 使用欧拉定理计算-1 次方(逆元)
for i := mx-1; i > 0; i-- {
e2 := bits.TrailingZeros(uint(i))
x := i >> e2
e5 := 0
for x % 5 == 0 {
e5++
x /= 5
}
invF[i-1] = invF[i] * x % MOD
}
}
func power(b, q int) int {
res := 1
for ;q > 0; q /= 2 {
if q & 1 == 1 {
res = res * b % MOD
}
b = b * b % MOD
}
return res
}
// 从 n 中选取 m 的方案数
func comb(n, m int) int {
if m < 0 || m > n {
return 0
}
// n! * (m!)^-1 * ((n - m)!)^-1
return fac[n] * invF[m] % MOD * invF[n - m] % MOD * power(2, p2[n] - p2[m] - p2[n - m]) % MOD * power(5, p5[n] - p5[m] - p5[n - m]) % MOD
}
总结
const MOD = 1_000_000_007
// 加
res := (a + b) % MOD
// 减法
// 结果落在 [1, MOD - 1)
res := (a + b + MOD) % MOD
// 乘(注意使用 64 位整数)
res := a * b % MOD
// 除
// power 是快速幂
res := a * power(b, MOD - 2, MOD) % MOD