我差不多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点,我还没睡觉整晚试图解决这个问题。我可以编辑它,以便在需要的时候更清楚地睡几个小时。
答案 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();
}
}