【发布时间】:2019-09-18 12:25:44
【问题描述】:
以清晰易懂的方式重新发布,没有任何无法正确显示的复杂 MathJax:
为了好玩,我探索了一些计算机科学/数论挑战网站,他们提出了以下问题,具体如下:
让P(n) = sum{1<=k<=n} φ(k)
查找P(10^16)
我为此搜索了很长时间并尝试了不同的方法:
使用
φ(n)= n * product{1<=i<=k} (Pi-1)/Pi的公式,我尝试计算范围内的φ(n),但是对于大n,这变得非常低效。通过这种方法,我可以达到10^7。除此之外,它变得太慢了。我尝试了另一种更直接的方法。 Wikipedia 和 Wolfram Alpha 建议直接计算
P(n)的类似公式:
P(n) = sum {1<=k<=n} φ(k)= 0.5⋅(1+∑{1<=k<=n} μ(k)⋅⌊n/k⌋^2)
这个公式似乎更有希望。我试过了,并设法比10^7 走得更远,但离目标还很远。通过预先计算 Moebius 函数的筛子,我可以得到比10^9 少一点的结果。我的内存不足,无法再计算筛子中的值。而且即使可以,也需要很长时间,离10^16还很远。
下面是我用 Java 编写的第二种方法的部分代码:
public static BigInteger PhiSummatoryFunction (long limit)
{
BigInteger sum = BigInteger.ZERO;
int [] m = MoebiusSieve(limit);
for (int i=1;i<m.length;i++)
sum=sum.add(BigInteger.valueOf((long) (m[i]*Math.floor(limit/i)*Math.floor(limit/i))));
return sum.add(BigInteger.ONE).divide(BigInteger.ONE.add(BigInteger.ONE));
}
其中 MoebiusSieve 是一个函数,它使用类似 Eratosthenes 的方法计算 Moebius 函数值直到筛子中的某个限制。
- 在了解并实现了我在 Internet 上找到的用于该计算的新递归方法后:
P(n)=n(n+1)/2−∑{2
我可以计算高达 P(10^11) 的值,并且在最大内存分配的情况下,预先计算尽可能多的 φ(n) 以及因此我可以用于记忆的所有 P(n),我可以只计算 P(10^12)超过20分钟。一项重大改进,但距离P(10^16) 仍有一点距离。如果计算时间稍长一点也没关系,但我担心P(10^16) 会花费指数级更长的时间,从P(10^11) 和P(10^12) 之间计算时间的“跳跃”来看。我的记忆允许我“保存”最多350,000,000 φ(n) values或最多700,000,000 μ(k) values。也许有一种方法可以使用 μ(k) 值而不是 φ(n) 来执行求和?
我所有的计算都表明并表明我的递归是主要的时间消耗者。这很明显,但我相信它需要的时间比它应该的要长。我在递归代码下方发布了一些文档。在我看来,这是进行此计算的正确方法,但我的实现并不是最佳的。
public static BigInteger phiR (long limit, long [] s) // limit is 10^t, s is the sieve of precomputed values of `P(n)`. Can store maximum 350,000,000 values
{
if (limit<s.length)
return BigInteger.valueOf(s[(int) limit]);
BigInteger sum = BigInteger.valueOf(limit).multiply(BigInteger.valueOf(limit).add(BigInteger.ONE)).divide(BigInteger.valueOf(2)); // this corresponds to the n'th triangular number
BigInteger midsum1=BigInteger.ZERO; // the first sum
BigInteger midsum2=BigInteger.ZERO; // the second sum
long m = 2;
while (limit/m != limit/(m+1) && m*m<=limit) // computing the first sum, first for changing floor(limit/m) values
{
midsum1=midsum1.add(phiR((long) Math.floor(limit/m),s));
m++;
}
for (long k = m;k*k<=limit;k++) // once the floors become constant for some values,-->
{ // can check how many times the value appears, and multiply accordingly,-->
BigInteger midPhi = phiR((long) Math.floor(limit/k),s); // rather than compute the Phi every time
long q = 1;
while (limit/k==limit/(k+1)&&k*k<=limit)
{
q++;
k++;
}
k--;
midPhi=midPhi.multiply(BigInteger.valueOf(q));
midsum1=midsum1.add(midPhi);
}
for (long d=1;d*d<=limit;d++) // computing the second sum
if ((double)d!=Math.floor(limit/d))
midsum2=midsum2.add(BigInteger.valueOf((long) (Math.floor(limit/d)-Math.floor(limit/(d+1)))).multiply(phiR(d,s)));
sum=sum.subtract(midsum1).subtract(midsum2);
return sum;
}
除了数组之外,有人建议我使用 dictinaries 来获取 n 的大值,但我对此一无所知。是否可以进行另一项改进以使时间框架为一天左右?
【问题讨论】:
-
您能否分享方法 3 的链接和/或解释其背后的理论?
-
@גלעדברקן 当然,请在此处查看
qwr的回答:math.stackexchange.com/questions/316376/… -
谢谢。有趣的是,那里的算法是由 Daniel Fischer 提供的,他是写 SO answer 我在我的(现已删除)答案中分享的同一个人 :)
-
是的,我也注意到了。我认为我的递归由于某种原因比它应该的慢。我不知道如何使它更快。有人建议使用字典来存储
P(n)值。 -
我很困惑:不使用
P(floor(n/i))(在方法 3 中)是否意味着我们从一开始就需要P(5*10^15)的值?
标签: java arrays algorithm function optimization