NC584 Fibonacci sSum

题意

求斐波那契数列数列前n项和的前n项和的前n项和。

1. 暴力法

直接模拟即可。

class Solution {
public:
    /**
     * 
     * @param n int整型 
     * @return int整型
     */
    unordered_map<int, int> m;

    const int M = 1e9 + 7;

    int fib(int k) {
        if (m[k]) return m[k]; // 记忆化搜索

        if (k == 1 || k == 2) return 1;

        int res = (fib(k-1) + fib(k-2)) % M;

        m[k] = res;
        return res;
    }
    int getSum(int n) {
        m[1] = 1, m[2] = 1;

        int res = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                for (int k = 1; k <= j; k++)
                    res = (res + fib(k)) % M;
            }
        }

        return res;
    }
};
  • 时间复杂度: ,三重循环套一个fib序列的计算公式,fib的计算通过记忆化搜索可以优化到
  • 空间复杂度: ,定义一个长度为的map

2. 矩阵快速幂(正解)

n的范围高达,方法1肯定是错误的,那么我们看下怎么优化。

设斐波那契数列的第i项为,我们令:

表示的前缀和,即:

表示的前缀和,即:

我们要求的就是的前缀和,即:

那么我们要求的答案不就是吗?

我们继续推导:

整理得:

我们可以把上式构造一个矩阵如图所示:

设等号左边的列矩阵为,中间的0-1方阵为,则有:

这就是一个类似等比数列的矩阵关系,稍微推广一下可以得出:

P是已知的,也都是可以直接手算出来的,问题就变成了如何快速求. 朴素的做法是直接乘,但这样的复杂度也达到了,依然不符合要求。

这里用到了一个算法:矩阵快速幂

我们知道,矩阵的乘法和普通乘法一样,也是满足结合律的。因此我们可以使用一种 分治 的思路,把A的n次方拆成规模为的方案:

这样我们就可以逐渐把n以二分的方式降阶,最终实现的时间内计算矩阵的n次幂。

实现中,我们可以对n进行二进制拆分,计算n的每一位二进制位是否有贡献,先贴上模板代码:

//泛型快速算a的n次幂
template <typename T>
T qpow(T a, ll n)
{
    T ans = E; // 赋值为乘法单位元,对于T是矩阵,赋值为单位矩阵
    while (n)
    {
        if (n & 1)
            ans = ans * a;
        n >>= 1;
        a = a * a;
    }
    return ans;
}

以计算为例, 演示上述代码的执行流程:

图片说明

如上所述,我们把20次乘法优化到5次乘法。这样我们将一个的算法逐步优化到, 这道题就可以过了。

class Solution {
public:
    /**
     * 
     * @param n int整型 
     * @return int整型
     */

    using ll = long long;
    using vll = vector<ll>;
    using mat = vector<vll>; // 定义矩阵

    const int M = 1e9 + 7;

    mat E;

    // 朴素的矩阵乘法, 直接算就可以了
    mat mul(mat &A, mat &B) {
        mat res(5, vll(5, 0));
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                for (int k = 0; k < 5; k++) {
                    ll &p = res[i][j];
                    p += A[i][k] * B[k][j];
                    p %= M;
                }
            }
        }
        return res;
    }
    // 矩阵快速幂
    mat qpow(mat A, ll n) {
        mat ans(E); // 初始化为单位矩阵,n=0时直接返回E

        while (n) {
            if (n&1) 
                ans = mul(ans, A);
            n >>= 1;
            A = mul(A, A);
        }

        return ans;
    }
    int getSum(int n) {
        E.resize(5, vll(5, 0));
        for (int i = 0; i < 5; i++)
            E[i][i] = 1;

        // p是上面证明出来的
        mat p = {
            {1, 1, 1, 1, 1},
            {0, 1, 1, 1, 1},
            {0, 0, 1, 1, 1},
            {0, 0, 0, 1, 1},
            {0, 0, 0, 1, 0}
        };

        // 算P的n-1次方
        p = qpow(p, n-1);

        // A1是(1,1,1,1,0) 直接手算出来的
        // p的n-1次方已经知道了,算出res就可以了
        ll res = 0;
        for (int i = 0; i < 4; i++)
            res += p[0][i];

        return res % M;
    }
};
  • 时间复杂度: ,矩阵快速幂的复杂度是, 朴素的m阶矩阵乘法复杂度是, 本题的,是个常数。
  • 空间复杂度: ,定义了阶数为5的矩阵,也视为常数。