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的矩阵,也视为常数。

京公网安备 11010502036488号