Compute nCr%p Using Fermat Little Theorem in Java

2025年5月7日 | 阅读7分钟

使用费马小定理可以有效地处理计算组合数模素数 p 的任务。组合公式 nCr 表示从 n 个元素的集合中选择 r 个元素的总方法数。费马小定理提供了一种计算模逆的有效方法,从而简化了该过程。

组合公式为

nCr = n! / (r!(n - r)!)

它表示从 n 个元素的集合中选择 r 个元素的数量,其中

  • nCr 是二项式系数
  • n! 是 n 的阶乘,
  • r! 是 r 的阶乘,
  • (n-r)! 是 n-r 的阶乘。

示例 1

输入:n = 10, r = 2, p = 13

输出 6

解释

10C2 = 10! / (2! * (10 - 2)!) = 10 * 9 / (2 * 1) = 45

45 % 13 = 6

示例 2

输入:n = 6, r = 2, p = 13

输出 2

解释

6C2 = 6! / (2! * (6 - 2)!) = 6 * 5 / (2 * 1) = 15

15 % 13 = 2

费马小定理与模逆

费马小定理指出,如果 p 是一个素数,那么对于任何整数 a,a^p - a 的值都可以被 p 整除。使用模运算,这可以表示为

ap = a (mod p)

例如,如果 a = 2 且 p = 7,则 27 = 128,并且 128 - 2 = 7 × 18 是 7 的整数倍。

如果 a 不能被 p 整除,费马小定理指出 a^(p-1) - 1 可以被 p 整除。在模运算中,这写为

ap-1 = 1 (mod p)

如果我们用 a-1 乘以两边,则得到。

ap-2 = a-1 (mod p)

因此,我们可以将模逆找到为 p-2

使用模运算预先计算阶乘

这种方法涉及预先计算所有小于等于 n 的值的模 p 的阶乘,这允许高效地计算组合数。通过存储阶乘值并使用费马小定理进行模逆计算,我们可以在预先计算后以常数时间计算 nCr mod p。

算法

步骤 1:检查 n<r:如果为真,则返回 0(因为从 n 个元素中选择 r 个是不可能的)。

步骤 2:检查 r=0:如果为真,则返回 1(因为 nC0=1)。

步骤 3:计算模 p 的阶乘:初始化一个阶乘数组 factorial[] 来存储从 1! 到 n! 的模 p 的阶乘值。

步骤 4:计算模逆:使用费马小定理,使用公式 a^(p-2) mod p 计算 factorial[r] 和 factorial[n-r] 的模逆。

步骤 5:应用组合公式:将 nCr mod p 计算为

nCr % p = (factorials[n] * inverseMod(factorials[r], p) * inverseMod(factorials[n - r], p)) % p

步骤 6:返回结果:打印 nCr mod p 的结果。

实施

文件名:CombinationModulo.java

输出

 
The value of 10C2 modulo 13 is: 6   

时间复杂度:O(n+log(mod))。

辅助空间复杂度:O(n)。

我们可以通过消除存储阶乘的单独数组的需要来优化空间复杂度。取而代之的是,我们可以在应用约简时直接乘以数字。

算法

步骤 1:通过重复平方基数和减半指数来高效地计算 base^exp % mod。

步骤 2:使用费马小定理查找 number^-1 % mod,即 number^(mod-2) % mod。

步骤 3:将两个数字模 mod 相乘,或通过计算分母的模逆来除。

步骤 4:计算 nCr

  • 如果 n < r,则返回 0。
  • 如果 r == 0,则返回 1。
  • 使用对称性:如果 r > n-r,则 **nCr** = nC(n-r)
  • 在模 mod 下,将分子项相乘,然后除以分母项。

步骤 5:调用函数计算 nCr % mod 并打印结果。

实施

文件名:CombinationCalculator.java

输出

 
Result of nCr % p is: 6   

时间复杂度:O(r⋅log(mod))

辅助空间复杂度: O(1)