更快的算法找到fibonacci n mod m

时间:2016-06-11 15:53:13

标签: java algorithm fibonacci

我差不多3天撞到墙上找到一个快速算法来找到fib n mod m,也就是说,斐波那契数n的余数除以m,其中:1< = n < = 10 ^ 18和2< = m< = 10 ^ 5

作为一个例子,我给出了:

Input: 281621358815590 30524 // n and m
Output: 11963

通过此测试,我的代码可以工作,但是测试失败了: 100 100000

这是我的代码:

import java.math.BigInteger;
import java.util.Scanner;

public class FibonacciHuge {
    private static BigInteger fibmod(long n, BigInteger m) {
        BigInteger a=BigInteger.ZERO;
        BigInteger b=BigInteger.ONE;
        BigInteger c;
        long i;
        for (i=2; i<=n;i++){
            c=a.add(b);
            a=b;
            b=c;
        }
        return b.mod(m);
      }

    private static BigInteger fibComplex (long n, BigInteger m) {
        int count = 2;
        for (int i = 2; i < (m.pow(2)).longValue()-1; i++) {
            long a2=fibmod(i+1,m).longValueExact();
            long a3=fibmod(i+2,m).longValueExact();
            count= count+1;
            if (a2==0 && a3==1){
                break;
            }

        }   
        return fibmod(n % count,m);
    }

      public static void main(String args[]) {
        Scanner in = new Scanner(System.in);
        long n = in.nextLong();
        BigInteger m = in.nextBigInteger();
        System.out.println(fibComplex(n,m));
        in.close();
      }
}

fibmod()中,我发现n的斐波纳契,最后我用m作为调制器。 在fibComplex()中,我想要找到Pisani周期的长度,所以我将reduce n用于n % (lengthofPisaniPeriod)的余数,然后应用mod m(所以n不是那么大)。 对于Java来说,这应该在1.5秒内完成,但对于大的Pisani时期(100,000),它太多了。 有些朋友说他们没有先找到Fib n,只是迭代找到周期的长度,然后用尖端将n减少到余下的n % (length of period)

我已经搜索了最快的斐波那契算法here但是解决方案似乎更容易,关于减少n,正如我之前描述的那样,但我可以在3天后掌握这个概念。我正在使用BigIntegers,但我不确定它是否真的需要,因为提示说:

  

“在这个问题中,给定的数字n可能非常大。因此,   n次迭代的算法循环不适合一秒钟   当然。因此,我们需要避免这样的循环“。

你可以找到一个Pisani周期/周期计算器here,即使对于大数字也能快速工作,所以我希望我能知道他们使用的算法。

对不起,如果我的问题中的某些内容不清楚,那就是上午11点,我还没睡觉整晚试图解决这个问题。我可以编辑它,以便在需要的时候更清楚地睡几个小时。

3 个答案:

答案 0 :(得分:3)

您使用的动态方法不够快。 您可以尝试矩阵取幂,甚至更好,快速加倍。 我们的想法是给定F(k)和F(k + 1),我们可以计算出这些:

F(2k)= F(k)[2F(k + 1)-F(k)]
F(2k + 1)= F(k + 1)^ 2 + F(k)^ 2

在Java中,它将是这样的:

private static BigInteger fastFibonacciDoubling(int n) {
        BigInteger a = BigInteger.ZERO;
        BigInteger b = BigInteger.ONE;
        int m = 0;
        for (int i = 31 - Integer.numberOfLeadingZeros(n); i >= 0; i--) {
            // Loop invariant: a = F(m), b = F(m+1)

            // Double it
            BigInteger d = multiply(a, b.shiftLeft(1).subtract(a));
            BigInteger e = multiply(a, a).add(multiply(b, b));
            a = d;
            b = e;
            m *= 2;

            if (((n >>> i) & 1) != 0) {
                BigInteger c = a.add(b);
                a = b;
                b = c;
                m++;
            }
        }
        return a;
    }

应用此方法而不是您在答案中插入的方法,您将看到不同之处。 ;)

您可以在https://www.nayuki.io/page/fast-fibonacci-algorithms找到有关不同Fibonacci方法的更多比较。

答案 1 :(得分:2)

诀窍是在计算时保持数字变得过大。让我们回到第一个程序。这是一个数学事实:(a + b)mod M ==((a mod M)+(b mod M))。因此:更改程序以计算'c'mod M;然后在最后你只需要直接返回'b'。

我知道每次迭代计算mod M看起来都很昂贵,但它看起来不会那么糟糕,因为'a'和'b'保持小于M.事实上,如果你这样做,你就不要甚至需要使用'mod()'函数:'a'和'b'在整个循环中保持在M下,因此'c'的下一个值将低于2M;你只需要检查它是否超过M,如果是,则减去M -once-。事实上,我会按如下方式编写'c'的代码(当然,从概念上讲,你需要使用BigNum方法):

    c = a + b;
    c1 = c - M;   // need to declare c1
    if ( !c1.negative() ) c = c1;

[我这样做是因为'c&gt; = M'和'c = c - M'几乎是同样的操作,所以为什么要花两倍的费用?当然我也假设BigNum有一个比零比较更快的符号测试方法。

答案 2 :(得分:1)

这是通过22次测试的最终解决方案。它有点草率,可能可以重构,但有2小时的睡眠,我很高兴它有用。

import java.math.BigInteger;
import java.util.Scanner;

public class FibonacciHugev2 {

    private static long lenP(BigInteger m) {
        long q=m.longValueExact();
        long a=0;
        long b=1;
        long c=0;
        long i;
        int count = 0;
        for (i=0; i<=(q*q)-1;i++){
            if (a==0 && b==1 && count > 0){
                break;
            }

            c=(a+b) % q;
            a=b % q;
            b=c % q;
            count = count + 1;  
        }
        return count;
      }

    private static BigInteger fibmod(long n, BigInteger m) {
        long r=n % lenP(m); 
        BigInteger a=BigInteger.ZERO;
        BigInteger b=BigInteger.ONE;
        BigInteger c;
        long i;
        if (r==0){
            return BigInteger.ZERO;
        }
        if (r==1){
            return BigInteger.ONE;
        }
        for (i=2; i<=r;i++){
            c=a.add(b);
            a=b;
            b=c;
        }
        return b.mod(m);
      }

      public static void main(String args[]) {
        Scanner in = new Scanner(System.in);
        long n = in.nextLong();
        BigInteger m = in.nextBigInteger();
        //System.out.println(lenP(m));
        System.out.println(fibmod(n,m));
        in.close();
      }
    }