zoukankan      html  css  js  c++  java
  • 斐波那契数列的O(logN)求法

    欢迎来访

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

    快速幂

    题目链接

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

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

    做法

    首先预处理出:(a^{2^0}, a^{2^1}, a^{2^2}, a^{2^3}, ..., , a^{2^{log b}})

    将每一项相乘,可以得到:(a^{2^0+2^1+2^2+2^3+...+2^{log b}})

    我们知道:(2^0+2^1+2^2+2^3+...+2^{log b})可以转换成二进制表示:(1111...111)一共有(log b + 1)

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

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

    C++代码

    #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
    ", qmi(a, b, p));
        }
        return 0;
    }
    

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

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

    [f(n) = egin{cases} 1, & ext{$n = 1$} \\[2ex] 1, & ext{$n = 2$} \\[2ex] f(n-1)+f(n-2), & ext{$n ge 2$} end{cases} ]

    我们设行向量(F_n=[f_n, f_{n+1}]),则:

    (F_1=[f_1, f_2])
    (F_2=[f_2, f_3])

    我们看一下如何构造矩阵(A)使得(F_1 cdot A)得到(F_2)

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

    (A=egin{bmatrix} 0 & 1 \ 1 & 1 \ end{bmatrix})

    所以(F_2=F1 cdot A)(F_3=F2 cdot A),因为矩阵的乘法满足结合律,进而得到:

    (F_n=F_1underbrace{Acdot Acdots A}_{ ext{ n-1 times}}),即(F_n=F_1 cdot A^{n-1})

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

    C++代码

    #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 项和(O(log n))

    题目链接

    分析

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

    我们设行向量(F_n=[f_n, f_{n+1}, S_n]),则:

    (F_1=[f_1, f_2, S_1])
    (F_2=[f_2, f_3, S_2])

    构造矩阵(A)使得(F_1 cdot A=F_2),不难发现:

    (A=egin{bmatrix} 0 & 1 & 0 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \\ end{bmatrix})

    C++代码

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

  • 相关阅读:
    edgeR
    R中的运算符,条件语句,控制语句
    library-type:fr-unstanded vs fisrt-stand vs second-stanrd
    R的几个基础函数
    HTseq-count
    HISAT2的运用
    shell的符号总结
    python-tuple
    python -List
    win10 ubuntu18.0 LTS双系统安装
  • 原文地址:https://www.cnblogs.com/optimjie/p/12707530.html
Copyright © 2011-2022 走看看