斐波那契数列的O(logN)求法

欢迎来访

介绍求斐波那契数列时间复杂度为 O ( log ⁡ N ) O(\log N) O(logN)的做法之前,我们先看一下快速幂。

快速幂

题目链接

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

当我们要求 a b m o d p , ( 1 ≤ a , b , p ≤ 1 0 9 ) a^b mod p, (1 \le a, b, p \le 10^9) abmodp,(1a,b,p109)时,如果是朴素做法,时间复杂度为 O ( N ) O(N) O(N)显然会超时,而快速幂能够做到的是将时间复杂度降到 O ( log ⁡ b ) O(\log b) O(logb)

做法

首先预处理出: a 2 0 , a 2 1 , a 2 2 , a 2 3 , . . . , , a 2 log ⁡ b a^{2^0}, a^{2^1}, a^{2^2}, a^{2^3}, ..., , a^{2^{\log b}} a20,a21,a22,a23,...,,a2logb

将每一项相乘,可以得到: a 2 0 + 2 1 + 2 2 + 2 3 + . . . + 2 log ⁡ b a^{2^0+2^1+2^2+2^3+...+2^{\log b}} a20+21+22+23+...+2logb

我们知道: 2 0 + 2 1 + 2 2 + 2 3 + . . . + 2 log ⁡ b 2^0+2^1+2^2+2^3+...+2^{\log b} 20+21+22+23+...+2logb可以转换成二进制表示: 1111...111 1111...111 1111...111一共有 log ⁡ b + 1 \log b + 1 logb+1

利用 2 i , 0 ≤ i ≤ log ⁡ b 2^i, 0 \le i \le \log b 2i,0ilogb每一项选与不选可以凑出, 0 0 0 ~ 2 log ⁡ b + 1 − 1 2^{\log b + 1} - 1 2logb+11的任意整数。其中就包括我们要凑出的: b b b

这一步的时间复杂度为 O ( log ⁡ b ) O(\log b) O(logb)

C++代码

#include 
#include 
#include 
#include 

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;
}

斐波那契数列 O ( log ⁡ n ) O(\log n) O(logn)求法

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

f ( n ) = { 1 , n = 1 1 , n = 2 f ( n − 1 ) + f ( n − 2 ) , n ≥ 2 f(n) = \begin{cases} 1, & \text{$n = 1$} \\\\[2ex] 1, & \text{$n = 2$} \\\\[2ex] f(n-1)+f(n-2), & \text{$n \ge 2$} \end{cases} f(n)=1,1,f(n1)+f(n2),n=1n=2n2

我们设行向量 F n = [ f n , f n + 1 ] F_n=[f_n, f_{n+1}] Fn=[fn,fn+1],则:

F 1 = [ f 1 , f 2 ] F_1=[f_1, f_2] F1=[f1,f2]
F 2 = [ f 2 , f 3 ] F_2=[f_2, f_3] F2=[f2,f3]

我们看一下如何构造矩阵 A A A使得 F 1 ⋅ A F_1 \cdot A F1A得到 F 2 F_2 F2

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

A = [ 0 1 1 1 ] A=\begin{bmatrix} 0 & 1 \\ 1 & 1 \\ \end{bmatrix} A=[0111]

所以 F 2 = F 1 ⋅ A F_2=F1 \cdot A F2=F1A F 3 = F 2 ⋅ A F_3=F2 \cdot A F3=F2A,因为矩阵的乘法满足结合律,进而得到:

F n = F 1 A ⋅ A ⋯ A ⏟  n-1 times F_n=F_1\underbrace{A\cdot A\cdots A}_{\text{ n-1 times}} Fn=F1 n-1 times AAA,即 F n = F 1 ⋅ A n − 1 F_n=F_1 \cdot A^{n-1} Fn=F1An1

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

C++代码

#include 
#include 
#include 
#include 

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 项和 O ( log ⁡ n ) O(\log n) O(logn)

题目链接

分析

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

我们设行向量 F n = [ f n , f n + 1 , S n ] F_n=[f_n, f_{n+1}, S_n] Fn=[fn,fn+1,Sn],则:

F 1 = [ f 1 , f 2 , S 1 ] F_1=[f_1, f_2, S_1] F1=[f1,f2,S1]
F 2 = [ f 2 , f 3 , S 2 ] F_2=[f_2, f_3, S_2] F2=[f2,f3,S2]

构造矩阵 A A A使得 F 1 ⋅ A = F 2 F_1 \cdot A=F_2 F1A=F2,不难发现:

A = [ 0 1 0 1 1 1 0 0 1 ] A=\begin{bmatrix} 0 & 1 & 0 \\\\ 1 & 1 & 1 \\\\ 0 & 0 & 1 \\\\ \end{bmatrix} A=010110011

C++代码

#include 
#include 
#include 
#include 

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蓝桥杯
求解斐波那契数列的若干方法

你可能感兴趣的:(AcWing蓝桥杯)