快速求组合数

快速求组合数

同余定理

给定一个正整数 m,如果两个整数 a 和 b 满足 a - b 能够被 m 整除,即 a − b m \frac{a-b}{m} mab 得到一个整数,那么就称整数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×k1=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×k1 (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 ap1 % 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 ap1 % p=1

a p − 2 × a   %   p = 1 a^{p-2} \times a \ \% \ p = 1 ap2×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!(nm)!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×(nm)!%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×(nm)!)%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!(nm)!n! % p=m!(nm)!n!×(M×m!)%p×(NM×(nm)!)%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)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值