【问题标题】:Finding remainder mod involving exponent and division involving huge numbers查找涉及指数和除法涉及大量数字的余数模
【发布时间】:2015-07-04 12:45:43
【问题描述】:

我需要一个快速算法来评估以下内容

((a^n-1)/(a-1)) % p

an 几乎相等,但小于 10^6,p 是一个固定的质数(比如p=1000003)。我需要在 1 秒内计算它。我正在使用python。 Wolfram Mathematica computes it instantly。使用以下代码需要 35.2170000076 秒

print (((10**6)**(10**6)-1)/((10**6)-1))%1000003

如果分母a-1 不存在,我可以将幂分组为更小的顺序并使用关系a*b (mod c) = (a (mod c) * b (mod c)) (mod c) 但分母存在。

如何使用快速算法对此进行评估?没有可用的 numpy/scipy。

更新::这是我想出的最终代码

def exp_div_mod(a, n, p):
    r = pow(a, n, p*(a-1)) - 1
    r = r - 1 if r == -1 else r
    return r/(a-1)

【问题讨论】:

  • 可以使用en.wikipedia.org/wiki/Extended_Euclidean_algorithm求逆1/(a-1) mod p;然后应用你写的最后一个身份。
  • @hiroprotagonist 我会尽力让你知道的。
  • gmpy 模块有一个可以计算模数逆的函数:gmpy.divm "返回 x 使得 b*x==a 模 m,否则如果没有这样的值 x,则引发 ZeroDivisionError 异常存在”。在 OP 中使用 anp(pow(a,n,p)-1)*gmpy.divm(1,a-1,p) % p 返回 444446。
  • 当然,gmpy 不是必需的,如果你做一点代数,就像 samgak 的回答一样。 :)
  • r = r - 1 if r == -1 else r 在您的更新中是错误的。

标签: python algorithm math language-agnostic


【解决方案1】:

(((a ** n) - 1) / (a-1)) % p

可以改写为

(((a ** n) - 1) % ((a-1)*p)) / (a-1)

这部分:

(((a ** n) - 1) % ((a-1)*p))

可以通过计算来计算:

((a ** n) % ((a-1)*p))

然后调整为 -1。

a 的 n 次方和 mod 的 ((a-1)*p)。这可以使用 Python pow() 函数来完成。然后调整为 -1 并除以 a-1。

使用 pow() 函数并传递模值比计算完整指数然后取模更快,因为模可以应用于计算的每个阶段的部分乘积,这会阻止值得到太大(106 的 106 次方有 600 万个十进制数字,在每一步都应用模数,这些值永远不必增长到大于模 - 在本例中约为 13 位)。

代码:

def exp_div_mod(a, n, p):
    m = p * (a - 1)
    return ((pow(a, n, m) - 1) % m) // (a - 1);

print exp_div_mod((10**6), (10**6), 1000003)

输出:

444446

注意:此方法仅适用于 a、n 和 p 为整数时。

【讨论】:

  • 不错!但是您不需要该循环:内置 Python pow 函数将模数作为可选的第三个参数。所以你可以做m = p * (a - 1); x = ((pow(a, n, m) - 1) % m) // (a - 1)
  • @NiklasB.: 为什么它不起作用?请注意,((a ** n) - 1) / (a-1) 是一个整数(几何级数之和)。设q/b 为整数,q/b = k*p + r。然后q=k*(p*b)+(r*b)
  • @NiklasB。 FWIW,我刚刚针对(a**n - 1) / (a - 1)) % p 测试了 samgak 的算法,随机选择了 10000 个 1
  • 没关系,我没有正确阅读答案。扩展模数的好技巧,非常聪明:)
  • 您也可以将函数的倒数第二行缩短为 return (pow(a, n, m) - 1) % m / (a-1),但这只是一个偏好问题
【解决方案2】:

(an−1) ⁄ (a−1) 是 的总和i = 0 到 n−1 of ai

计算后一个 mod p 很简单,基于以下几点:

F(a, n) 为 Σ(i=0..n -1){ai} 如果 n > 0,否则为 0。

现在:

  1. F(a,n) = a×F(a,n−1) + 1

  2. F(a,2n) = (a+1)×F(a2,n)

第二个身份是分治递归。

由于这两个都只涉及加法和乘法,我们可以计算它们 mod p 而不需要大于 a×p 的整数类型通过分布模运算。 (见下面的代码。)

只需第一次递归,我们就可以编写一个迭代解决方案:

def sum_of_powers(a, n, p):
  sum = 0
  for i in range(n): sum = (a * sum + 1) % p
  return sum

同样使用分而治之的递归,我们得到的东西并不复杂:

def sum_of_powers(a, n, p):
  if n % 2 == 1:
    return (a * sum_of_powers(a, n-1, p) + 1) % p
  elif n > 0:
    return ((a + 1) * sum_of_powers(a * a % p, n // 2, p)) % p
  else:
    return 0

第一个解决方案在不到一秒的时间内返回,n == 106。第二个立即返回,即使 n 为 109

【讨论】:

  • 你可以不用循环吗?在一个循环中汇总一百万左右的 mod 功率并不是很快。
  • @PM2Ring 有一个技巧,您可以将奇数项和偶数项分开并重用偶数被加数的总和来计算 O(1) 中奇数项的总和,从而产生一个仅使用 O (log n) 加法和乘法
  • @pm2ring:如果将 a^i%p startimg 与 i=0 相加,则每个项“仅”需要一个乘法和一个 mod(利用前一项的值);你永远不需要计算幂。在现代硬件上可以在一秒钟内轻松完成一百万次这样的计算。但 NiklasB 的建议更快
  • @NiklasB。有趣的!我认为我知道这种分而治之的方法将如何在 O(log n) 中发挥作用。
  • @rici 相信我,我在此处发布答案问题之前尝试过此操作。我考虑了 Nicholas B 提出的方法,但发现编码太多。
【解决方案3】:

您可以乘以p - 1modular inverse。由于费马小定理,你有 xp-2 · x ≡ xp-1 ≡ 1 (mod p) 对于所有 0 ,因此您甚至不需要扩展 Euclid 来计算逆,只需标准 Python 中的 pow 函数:

(pow(a, n, p) - 1) * pow(a - 1, p - 2, p) % p

该算法具有时间复杂度?(log p),因为使用了square-and-multiply

【讨论】:

  • 我猜你的意思是 a-1 的倒数,而不是 p-1
  • 嗨,我不明白你的第一种方法,你能写一些数学吗?我知道费马定理,但在它之后??
  • @dread_cat_pirate 您可以通过乘以模逆 x^(-1) 来除以一个模数 x 模素数。通常你会使用扩展欧几里德算法来计算逆,但在素数模的情况下,逆只是 x^(p-2),由于费马定理
  • 我理解另一个答案,但我仍然没有从数学上得到它。由于费马,x 是 x^(p-2) 的模逆,但是this relation 是真的吗?
  • 遗憾的是我们不能在这里使用 LaTeX,比如SE.Mathematics...
猜你喜欢
  • 2020-01-22
  • 2015-12-31
  • 1970-01-01
  • 2019-05-29
  • 2020-08-12
  • 1970-01-01
  • 1970-01-01
  • 2016-07-26
  • 1970-01-01
相关资源
最近更新 更多