斐波那契数列的 O(logN) 求法
2020-04-15 15:08:17
本文总阅读量

介绍求斐波那契数列时间复杂度为的做法之前,我们先看一下快速幂。

快速幂

题目链接

快速幂是数论中非常基础的算法。

当我们要求时,如果是朴素做法,时间复杂度为显然会超时,而快速幂能够做到的是将时间复杂度降到

做法

首先预处理出:

将每一项相乘,可以得到:

我们知道:可以转换成二进制表示:一共有

利用每一项选与不选可以凑出, ~ 的任意整数。其中就包括我们要凑出的:

这一步的时间复杂度为

C++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

int qmi(int a, int b, int p) {
LL res = 1 % p;
while (b) {
if (b & 1) res = res * a % p;
b >>= 1;
a = (LL)a * a % p;
}
return res;
}

int main() {
int n;
int a, b, p;
scanf("%d", &n);
while (n--) {
scanf("%d%d%d", &a, &b, &p);
printf("%lld\n", qmi(a, b, p));
}
return 0;
}

斐波那契数列求法

首先先看一下斐波那契数列。

我们设行向量,则: > >

我们看一下如何构造矩阵使得得到

这个只要知道矩阵的乘法就不难构造出: >$A=

$

所以,因为矩阵的乘法满足结合律,进而得到:

,即

这样我们就可以用快速幂来求了。

C++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int MOD = 1e9 + 7;

LL res[2] = {1LL, 1LL};
LL A[2][2] = {
{0LL, 1LL},
{1LL, 1LL}
};

void mul(LL c[2], LL a[2], LL b[][2]) {

LL tmp[2] = {0};
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
tmp[i] = tmp[i] + (a[j] * b[j][i]) % MOD;

memcpy(c, tmp, sizeof tmp);
}

void mul(LL c[][2], LL a[][2], LL b[][2]) {

LL tmp[2][2] = {0};
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 2; k++)
tmp[i][j] = tmp[i][j] + (a[i][k] * b[k][j]) % MOD;

memcpy(c, tmp, sizeof tmp);
}

LL fib(int n) {

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

return res[0];
}


int main() {

int n;
scanf("%d", &n);

printf("%lld", fib(n));

return 0;
}

拓展:求斐波那契前 n 项和

题目链接

分析

与上面的思路相同,在行向量中再加上和

我们设行向量,则: > >

构造矩阵使得,不难发现:

$A=

$

C++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

int n, m;

int res[3] = {1, 1, 1};
int A[3][3] = {
{0, 1, 0},
{1, 1, 1},
{0, 0, 1}
};

void mul(int c[3], int a[3], int b[][3]) {

int tmp[3] = {0};
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
tmp[i] = (tmp[i] + (LL)a[j] * b[j][i]) % m;

memcpy(c, tmp, sizeof tmp);
}

void mul(int c[][3], int a[][3], int b[][3]) {

int tmp[3][3] = {0};
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
for (int k = 0; k < 3; k++)
tmp[i][j] = (tmp[i][j] + (LL)a[i][k] * b[k][j]) % m;

memcpy(c, tmp, sizeof tmp);
}

int main() {

scanf("%d%d", &n, &m);

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

printf("%d", res[2]);

return 0;
}

参考

AcWing蓝桥杯 求解斐波那契数列的若干方法