【问题标题】:Finding the nth fib number, in O(logn)在 O(logn) 中找到第 n 个 fib 编号
【发布时间】:2015-08-28 13:56:40
【问题描述】:

我正在尝试解决这个问题:SPOJ problem

经过一些研究,我发现它归结为对第 n 个 fib 数的简单计算,但是 n 可能会变得非常大,因此 O(n) 解决方案不会有任何好处。谷歌搜索,我发现你可以计算出 O(logn) 中的第 n 个 fib 数,还有一个代码示例可以做到这一点:

long long fibonacci(int n) {
    long long fib[2][2] = {{1,1},{1,0}}, ret[2][2] = {{1,0},{0,1}}, tmp[2][2] = {{0,0},{0,0}};
    int i, j, k;
    while (n) {
        if (n & 1) {
            memset(tmp, 0, sizeof tmp);
            for (i = 0; i < 2; i++)
                for (j = 0; j < 2; j++)
                    for (k = 0; k < 2; k++)
                        tmp[i][j] = (tmp[i][j] + ret[i][k] * fib[k][j]);
            for (i = 0; i < 2; i++)
                for (j = 0; j < 2; j++)
                    ret[i][j] = tmp[i][j];
        }
        memset(tmp, 0, sizeof tmp);
        for (i = 0; i < 2; i++)
            for (j = 0; j < 2; j++)
                for (k = 0; k < 2; k++)
                    tmp[i][j] = (tmp[i][j] + fib[i][k] * fib[k][j]);
        for (i = 0; i < 2; i++)
            for (j = 0; j < 2; j++)
                fib[i][j] = tmp[i][j];
        n /= 2;
    }
    return (ret[0][1]);
}

我尝试针对问题对其进行修改,但仍然收到 WA:http://ideone.com/3TtE5m

我计算模运算是否错误?还是有其他问题?

【问题讨论】:

  • 斐波那契或素数?
  • 对于 SPOJ 问题,使用 fib(n+1),除了 n = 0,我不确定 0 个硬币算不算 1 way。请注意, (x%12345678901) * y(%12345678901) 最多需要 68 位。在 64 位模式下,可以实现一个基于汇编的函数来对模 12345678901 进行乘法运算,因为乘法和除法之后的乘积可以是 128 位。

标签: c fibonacci


【解决方案1】:

你的意思是我希望的第 n 个斐波那契数。

为了做到这一点,您需要对here 描述的斐波那契数进行矩阵分解。

基本思想是您将 Donald E. Knuth 矩阵单位形式用于斐波那契数,即:

您将尝试找到矩阵的 (k) 次方,而不是用传统方式计算斐波那契数,其中 k 是给定的数。

所以这是解决 k 矩阵乘法中的问题,但并不是很有帮助,因为我们可以用更简单的方式来解决。

但是等等!我们可以优化矩阵乘法。我们可以先将其平方,然后再进行一半的乘法运算,而不是进行 k 次乘法运算。我们可以继续这样做。所以如果给定的数字是 2a 那么我们可以在 a 步骤中完成。通过保持对矩阵的平方。

如果数字不是 2 的幂,我们可以对数字进行二元分解,看看是否将给定的平方矩阵转化为最终产品。

在您的情况下,每次乘法后,您还需要将模运算符 123456 应用于每个矩阵元素。

如果没有看到更清晰和更长的链接,希望我的解释有所帮助。

该任务实际上还有一个警告:当您被要求提供一些以给定数字取模的斐波那契数时,您还应该证明取每个矩阵元素的余数不会改变结果。换句话说,如果我们将矩阵相乘并取余数,我们实际上仍然得到斐波那契数的余数。但由于余数运算在加法和乘法上是可分配的,它实际上确实会产生正确的结果。

【讨论】:

  • 我在提交我的 Mac 后才注意到做了完全相同的更正:D
  • 无论如何,我在 C 中得到了一个矩阵分解的代码示例,我该如何修改它以给出给定概率的正确答案?
  • 只需在乘法后将模运算符应用于每个矩阵字段。所以代替 tmp[i][j]=(tmp[i][j]+ret[i][k]*fib[k][j]);做 tmp[i][j]=(tmp[i][j]+ret[i][k]*fib[k][j]) % 123456;
  • 我就是这样做的,但法官说这是 WA ._。那么一定是打印错误。
  • orlp贴出来的lucas序列方法,如果矩阵乘和平方不能降到相当于lucas序列的程度,可能会更快。
【解决方案2】:

斐波那契数作为 的连分数的连续收敛的比率出现,并且由任何连分数的连续收敛形成的矩阵具有+1−1 的行列式。

矩阵表示给出了斐波那契数的以下封闭式表达式,即

矩阵乘以n 时间,因为只有这样我们才能得到(n+1)th 斐波那契数作为结果矩阵中行和列(0, 0) 的元素。

如果我们在不使用递归矩阵乘法的情况下应用上述方法,那么Time Complexity: O(n)Space Complexity: O(1)

但是我们想要Time Complexity: O(log n),所以我们必须优化上面的方法,这可以通过矩阵的递归乘法得到nth的幂。

上述规则的实现见下文。

#include <stdio.h>

void multiply(int F[2][2], int M[2][2]);

void power(int F[2][2], int n);

/*
The function that returns nth Fibonacci number.
*/

int fib(int n) {
    int F[2][2] = {{1, 1}, {1, 0}};
    if (n == 0)
        return 0;
    power(F, n - 1);
    return F[0][0];
}

/*
Optimized using recursive multiplication.
*/

void power(int F[2][2], int n) {
    if ( n == 0 || n == 1)
        return;
    int M[2][2] = {{1, 1}, {1, 0}};
    power(F, n / 2);
    multiply(F, F);
    if (n % 2 != 0)
        multiply(F, M);
}

void multiply(int F[2][2], int M[2][2]) {
    int x = F[0][0] * M[0][0] + F[0][1] * M[1][0];
    int y = F[0][0] * M[0][1] + F[0][1] * M[1][1];
    int z = F[1][0] * M[0][0] + F[1][1] * M[1][0];
    int w = F[1][0] * M[0][1] + F[1][1] * M[1][1];
    F[0][0] = x;
    F[0][1] = y;
    F[1][0] = z;
    F[1][1] = w;
}

int main() {
    printf("%d\n", fib(15));
    /*
    15th Fibonacci number is 610.
    */
    return 0;
}

【讨论】:

    【解决方案3】:

    有一个非常简单的算法,只使用整数:

    long long fib(int n) {
        long long a, b, p, q;
        a = q = 1;
        b = p = 0;
        while (n > 0) {
            if (n % 2 == 0) {
                long long qq = q*q;
                q = 2*p*q + qq;
                p = p*p + qq;
                n /= 2;
            } else {
                long long aq = a*q;
                a = b*q + aq + a*p;
                b = b*p + aq;
                n -= 1;
            }
        }
        return b;
    }
    

    这是基于identities of the Lucas sequence

    【讨论】:

    • 时间复杂度是多少,它是否能够评估假设 n=10^18?使用模算术?
    • @AleksXPO 您可以通过简单地在每一步取模来使其模块化。它应该在眨眼之间计算 10^18 - 它只需要大约 60 轮迭代。
    • 如果有人错过了我对原始问题的评论,请注意 (x%12345678901) * y(%12345678901) 最多需要 68 位。
    • @rcgldr 我在这里有一个 64 位模乘法的优化实现:github.com/orlp/libop/blob/…
    猜你喜欢
    • 2012-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2011-06-05
    • 2011-10-22
    • 2022-10-13
    相关资源
    最近更新 更多