计算上位函数的总和,直到10 ^ 16

时间:2019-04-29 21:06:49

标签: java arrays algorithm function optimization

以清晰易懂的方式重新发布它,而不会出现任何无法正常显示的复杂MathJax:

我探索了一些计算机科学/数论挑战网站,他们很有趣地提出了以下问题:

P(n) = sum{1<=k<=n} φ(k)

找到P(10^16)

我搜索了很长时间,并尝试了不同的方法:

  1. 使用φ(n)= n * product{1<=i<=k} (Pi-1)/Pi的公式,我尝试计算范围内的φ(n),但是对于大的n来说效率很低。通过这种方法,我可以达到10^7的程度。除此之外,它变得太慢了。

  2. 我尝试了另一种方法,更加直接。维基百科和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是一个函数,它使用类似于橡皮擦的方法在筛子中计算Moebius函数的值,直到某个极限。

  1. 理解并实现了我在互联网上发现的用于此计算的新的递归方法:

P(n)= n(n + 1)/ 2-∑ {2 <= i <=√n} P(⌊n/i⌋)-∑ {1 <= j <=√n} P( j)⋅(⌊n/ j⌋−⌊n /(j + 1)⌋)

我最多可以计算P(10^11)的值,并且在最大内存分配的情况下,尽可能多地预先计算φ(n),因此我可以进行记忆的所有P(n)都可以计算{ {1}}仅需20多分钟。一项重大改进,但与P(10^12)仍有一段距离。可以花更长的时间来确定,但是我担心P(10^16)将花费成倍的时间,这取决于P(10^16)P(10^11)之间的计算时间“跳跃”。我的记忆使我可以“保存”最多P(10^12)。也许有一种方法可以使用μ(k)值而不是φ(n)?

我所有的计算都表明并表明我的递归是主要的时间消耗者。这是显而易见的,但是我敢肯定它会花更长的时间。我在递归代码下面发布了一些文档。在我看来,这是进行此计算的正确方法,但我的实现并非最佳。

350,000,000 φ(n) values

除了数组之外,建议我将字典用于大的700,000,000 μ(k) values值,但我对此一无所知。是否可以进行其他改进以使时间范围一天左右?

2 个答案:

答案 0 :(得分:0)

如果您想知道单个数字 n 的总和,找到它的最佳方法是将 n 分解为因子,并将乘积减去每个因子1。 ;例如,30 = 2 * 3 * 5,然后从每个因子中减去1,然后相乘,得出的总和为1 * 2 * 4 =8。但是,如果要查找所有小于给定 n ,比将每个因子都分解的更好的方法是筛分。这个想法很简单:从0到 n 设置一个数组 X ,将 i 存储在每个 X_i 中,然后从0开始遍历数组,每当 X_i = i 遍历 i 的倍数时,将它们乘以( i -1)/ i 。您可以在末尾计算总和,也可以随时累加。由于筛子很大,因此您需要对其进行分段。

以下是我的博客中一些有用的页面:Sieving For TotientsSegmented Sieve of Eratosthenes。如果您在附近闲逛,可能还会发现其他有趣的东西。

答案 1 :(得分:0)

对于低阶订单:

public class Solution {

    static Map<Long,Long> X2 = new HashMap<>();

    static long F2(long N){
        return N*(N-1)/2;
    }

    static long R2(long N){
        if(N==1) return 0;
        if(X2.containsKey(N)) return X2.get(N);
        long sum = F2(N);
        long m=2;
        while(true){
            long x = N/m;
            long nxt = N/x;
            if(nxt >= N) {
                X2.put(N, sum - (N-m+1)*R2(N/m));
                return X2.get(N);
            }
            sum -= (nxt-m+1) * R2(N/m);
            m = nxt+1;
        }
    }

    public long odradi(int N){
        return R2(N)+1;
    }

    public static void main(String[] args) {
        System.out.println(R2(100000)+1);
    }
}