我有机会研究 GCD 和的计算,因为这个问题出现在一个名为 GCD Sum 的 HackerEarth 教程中。谷歌搜索出现了 some academic papers 有用的公式,我在这里报告,因为它们在 deviantfan 链接的 MathOverflow article 中没有提到。
对于互质 m 和 n(即 gcd(m, n) == 1),函数是乘法的:
gcd_sum[m * n] = gcd_sum[m] * gcd_sum[n]
素数 p 的 e 次幂:
gcd_sum[p^e] = (e + 1) * p^e - e * p^(e - 1)
如果只计算一个总和,那么这些公式可以应用于分解相关数字的结果,这仍然比重复 gcd() 调用或通过 rigmarole proposed by Толя 更快。
但是,这些公式可以很容易地用于有效地计算整个函数表。基本上,您所要做的就是将它们插入linear time Euler totient calculation 的算法中,您就完成了 - 这计算 all GCD 总和比计算单个 GCD 总和要快得多数字 10^6 通过调用 gcd() 函数。基本上,该算法有效地枚举了直到 n 的数字的最小因子分解,从而可以轻松计算任何乘法函数 - Euler totient(又名 phi)、sigmas,或者实际上是 GCD 和。
这里有一段哈希代码,用于计算 GCD 和的表以用于较小的限制 - “小”是指 sqrt(N) * N 不会溢出 32 位有符号整数。 IOW,它适用于 10^6 的限制(对于限制为 5 * 10^5 的 HackerEarth 任务来说已经足够了),但 10^7 的限制需要在几个战略位置坚持 (long) 演员表。然而,这种在更高范围内操作的功能强化留给读者作为众所周知的练习...... ;-)
static int[] precompute_Pillai (int limit)
{
var small_primes = new List<ushort>();
var result = new int[1 + limit];
result[1] = 1;
int n = 2, small_prime_limit = (int)Math.Sqrt(limit);
for (int half = limit / 2; n <= half; ++n)
{
int f_n = result[n];
if (f_n == 0)
{
f_n = result[n] = 2 * n - 1;
if (n <= small_prime_limit)
{
small_primes.Add((ushort)n);
}
}
foreach (int prime in small_primes)
{
int nth_multiple = n * prime, e = 1, p = 1; // 1e6 * 1e3 < INT_MAX
if (nth_multiple > limit)
break;
if (n % prime == 0)
{
if (n == prime)
{
f_n = 1;
e = 2;
p = prime;
}
else break;
}
for (int q; ; ++e, p = q)
{
result[nth_multiple] = f_n * ((e + 1) * (q = p * prime) - e * p);
if ((nth_multiple *= prime) > limit)
break;
}
}
}
for ( ; n <= limit; ++n)
if (result[n] == 0)
result[n] = 2 * n - 1;
return result;
}
正如所承诺的,这将在 12.4 毫秒内计算所有 GCD 总和,达到 500,000,而在同一台机器上通过 gcd() 调用计算 500,000 的单个总和需要 48.1 毫秒。该代码已针对 OEIS list of the Pillai function (A018804) 进行了高达 2000 次验证,并针对基于 gcd 的函数进行了高达 500,000 次验证——这项工作耗时整整 4 小时。
可以应用一系列优化来显着加快代码速度,例如将模除法替换为乘法(使用逆运算)和比较,或者通过步进“素数”来减少更多毫秒Cleaner-upper' 循环模 6。但是,我想以基本的、未优化的形式展示该算法,因为 (a) 它的速度非常快,并且 (b) 它可能对其他乘法函数有用,而不仅仅是 GCD总和。
PS:Granlund/Montgomery 论文Division by Invariant Integers using Multiplication 的第 9 节描述了通过与逆相乘进行的模测试,但很难找到有关高效计算 2 的逆模幂的信息。大多数来源使用扩展欧几里得算法或类似的矫枉过正。所以这里有一个计算乘法逆模 2^32 的函数:
static uint ModularInverse (uint n)
{
uint x = 2 - n;
x *= 2 - x * n;
x *= 2 - x * n;
x *= 2 - x * n;
x *= 2 - x * n;
return x;
}
这实际上是Newton-Raphson 的五次迭代,以防万一。 ;-)